diff options
Diffstat (limited to 'lisp/progmodes/python.el')
| -rw-r--r-- | lisp/progmodes/python.el | 246 |
1 files changed, 242 insertions, 4 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index d3ffc2db2c9..8368f4da513 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el | |||
| @@ -245,7 +245,9 @@ | |||
| 245 | (require 'ansi-color) | 245 | (require 'ansi-color) |
| 246 | (require 'cl-lib) | 246 | (require 'cl-lib) |
| 247 | (require 'comint) | 247 | (require 'comint) |
| 248 | (eval-when-compile (require 'subr-x)) ;For `string-empty-p'. | 248 | (eval-when-compile (require 'subr-x)) ;For `string-empty-p' and `string-join'. |
| 249 | (require 'treesit) | ||
| 250 | (require 'pcase) | ||
| 249 | 251 | ||
| 250 | ;; Avoid compiler warnings | 252 | ;; Avoid compiler warnings |
| 251 | (defvar compilation-error-regexp-alist) | 253 | (defvar compilation-error-regexp-alist) |
| @@ -265,6 +267,11 @@ | |||
| 265 | :version "24.3" | 267 | :version "24.3" |
| 266 | :link '(emacs-commentary-link "python")) | 268 | :link '(emacs-commentary-link "python")) |
| 267 | 269 | ||
| 270 | (defcustom python-use-tree-sitter nil | ||
| 271 | "If non-nil, `python-mode' tries to use tree-sitter. | ||
| 272 | Currently `python-mode' uses tree-sitter for font-locking, imenu, | ||
| 273 | and movement functions." | ||
| 274 | :type 'boolean) | ||
| 268 | 275 | ||
| 269 | 276 | ||
| 270 | ;;; Bindings | 277 | ;;; Bindings |
| @@ -899,6 +906,136 @@ is used to limit the scan." | |||
| 899 | "Dotty syntax table for Python files. | 906 | "Dotty syntax table for Python files. |
| 900 | It makes underscores and dots word constituent chars.") | 907 | It makes underscores and dots word constituent chars.") |
| 901 | 908 | ||
| 909 | ;;; Tree-sitter font-lock | ||
| 910 | |||
| 911 | ;; NOTE: Tree-sitter and font-lock works differently so this can't | ||
| 912 | ;; merge with `python-font-lock-keywords-level-2'. | ||
| 913 | |||
| 914 | (defvar python--treesit-keywords | ||
| 915 | '("as" "assert" "async" "await" "break" "class" "continue" "def" | ||
| 916 | "del" "elif" "else" "except" "exec" "finally" "for" "from" | ||
| 917 | "global" "if" "import" "lambda" "nonlocal" "pass" "print" | ||
| 918 | "raise" "return" "try" "while" "with" "yield")) | ||
| 919 | |||
| 920 | (defvar python--treesit-builtins | ||
| 921 | '("abs" "all" "any" "ascii" "bin" "bool" "breakpoint" "bytearray" | ||
| 922 | "bytes" "callable" "chr" "classmethod" "compile" "complex" | ||
| 923 | "delattr" "dict" "dir" "divmod" "enumerate" "eval" "exec" | ||
| 924 | "filter" "float" "format" "frozenset" "getattr" "globals" | ||
| 925 | "hasattr" "hash" "help" "hex" "id" "input" "int" "isinstance" | ||
| 926 | "issubclass" "iter" "len" "list" "locals" "map" "max" | ||
| 927 | "memoryview" "min" "next" "object" "oct" "open" "ord" "pow" | ||
| 928 | "print" "property" "range" "repr" "reversed" "round" "set" | ||
| 929 | "setattr" "slice" "sorted" "staticmethod" "str" "sum" "super" | ||
| 930 | "tuple" "type" "vars" "zip" "__import__")) | ||
| 931 | |||
| 932 | (defvar python--treesit-constants | ||
| 933 | '("Ellipsis" "False" "None" "NotImplemented" "True" "__debug__" | ||
| 934 | "copyright" "credits" "exit" "license" "quit")) | ||
| 935 | |||
| 936 | (defvar python--treesit-operators | ||
| 937 | '("-" "-=" "!=" "*" "**" "**=" "*=" "/" "//" "//=" "/=" "&" "%" "%=" | ||
| 938 | "^" "+" "+=" "<" "<<" "<=" "<>" "=" "==" ">" ">=" ">>" "|" "~" | ||
| 939 | "and" "in" "is" "not" "or")) | ||
| 940 | |||
| 941 | (defvar python--treesit-special-attributes | ||
| 942 | '("__annotations__" "__closure__" "__code__" | ||
| 943 | "__defaults__" "__dict__" "__doc__" "__globals__" | ||
| 944 | "__kwdefaults__" "__name__" "__module__" "__package__" | ||
| 945 | "__qualname__" "__all__")) | ||
| 946 | |||
| 947 | (defvar python--treesit-exceptions | ||
| 948 | '(;; Python 2 and 3: | ||
| 949 | "ArithmeticError" "AssertionError" "AttributeError" "BaseException" | ||
| 950 | "BufferError" "BytesWarning" "DeprecationWarning" "EOFError" | ||
| 951 | "EnvironmentError" "Exception" "FloatingPointError" "FutureWarning" | ||
| 952 | "GeneratorExit" "IOError" "ImportError" "ImportWarning" | ||
| 953 | "IndentationError" "IndexError" "KeyError" "KeyboardInterrupt" | ||
| 954 | "LookupError" "MemoryError" "NameError" "NotImplementedError" | ||
| 955 | "OSError" "OverflowError" "PendingDeprecationWarning" | ||
| 956 | "ReferenceError" "RuntimeError" "RuntimeWarning" "StopIteration" | ||
| 957 | "SyntaxError" "SyntaxWarning" "SystemError" "SystemExit" "TabError" | ||
| 958 | "TypeError" "UnboundLocalError" "UnicodeDecodeError" | ||
| 959 | "UnicodeEncodeError" "UnicodeError" "UnicodeTranslateError" | ||
| 960 | "UnicodeWarning" "UserWarning" "ValueError" "Warning" | ||
| 961 | "ZeroDivisionError" | ||
| 962 | ;; Python 2: | ||
| 963 | "StandardError" | ||
| 964 | ;; Python 3: | ||
| 965 | "BlockingIOError" "BrokenPipeError" "ChildProcessError" | ||
| 966 | "ConnectionAbortedError" "ConnectionError" "ConnectionRefusedError" | ||
| 967 | "ConnectionResetError" "FileExistsError" "FileNotFoundError" | ||
| 968 | "InterruptedError" "IsADirectoryError" "NotADirectoryError" | ||
| 969 | "PermissionError" "ProcessLookupError" "RecursionError" | ||
| 970 | "ResourceWarning" "StopAsyncIteration" "TimeoutError" | ||
| 971 | ;; OS specific | ||
| 972 | "VMSError" "WindowsError" | ||
| 973 | )) | ||
| 974 | |||
| 975 | (defun python--treesit-fontify-string (beg end _) | ||
| 976 | "Fontify string between BEG and END. | ||
| 977 | Do not fontify the initial f for f-strings." | ||
| 978 | (let ((beg (if (eq (char-after beg) ?f) | ||
| 979 | (1+ beg) beg))) | ||
| 980 | (put-text-property beg end 'face 'font-lock-string-face))) | ||
| 981 | |||
| 982 | (defvar python--treesit-settings | ||
| 983 | (treesit-font-lock-rules | ||
| 984 | :language 'python | ||
| 985 | `(;; Queries for def and class. | ||
| 986 | (function_definition | ||
| 987 | name: (identifier) @font-lock-function-name-face) | ||
| 988 | |||
| 989 | (class_definition | ||
| 990 | name: (identifier) @font-lock-type-face) | ||
| 991 | |||
| 992 | ;; Comment and string. | ||
| 993 | (comment) @font-lock-comment-face | ||
| 994 | |||
| 995 | (string) @python--treesit-fontify-string | ||
| 996 | ((string) @font-lock-doc-face | ||
| 997 | (:match "^\"\"\"" @font-lock-doc-face)) | ||
| 998 | (interpolation (identifier) @font-lock-variable-name-face) | ||
| 999 | |||
| 1000 | ;; Keywords, builtins, and constants. | ||
| 1001 | [,@python--treesit-keywords] @font-lock-keyword-face | ||
| 1002 | |||
| 1003 | ((identifier) @font-lock-keyword-face | ||
| 1004 | (:match "^self$" @font-lock-keyword-face)) | ||
| 1005 | |||
| 1006 | ((identifier) @font-lock-builtin-face | ||
| 1007 | (:match ,(rx-to-string | ||
| 1008 | `(seq bol | ||
| 1009 | (or ,@python--treesit-builtins | ||
| 1010 | ,@python--treesit-special-attributes) | ||
| 1011 | eol)) | ||
| 1012 | @font-lock-builtin-face)) | ||
| 1013 | |||
| 1014 | [(true) (false) (none)] @font-lock-constant-face | ||
| 1015 | |||
| 1016 | ;; Escape sequences | ||
| 1017 | (escape_sequence) @font-lock-constant-face | ||
| 1018 | |||
| 1019 | ;; Variable names. | ||
| 1020 | (assignment left: (identifier) | ||
| 1021 | @font-lock-variable-name-face) | ||
| 1022 | (pattern_list (identifier) | ||
| 1023 | @font-lock-variable-name-face) | ||
| 1024 | (tuple_pattern (identifier) | ||
| 1025 | @font-lock-variable-name-face) | ||
| 1026 | (list_pattern (identifier) | ||
| 1027 | @font-lock-variable-name-face) | ||
| 1028 | (list_splat_pattern (identifier) | ||
| 1029 | @font-lock-variable-name-face) | ||
| 1030 | |||
| 1031 | ;; Types and decorators. | ||
| 1032 | (decorator) @font-lock-type-face | ||
| 1033 | ((identifier) @font-lock-type-face | ||
| 1034 | (:match ,(rx-to-string | ||
| 1035 | `(seq bol (or ,@python--treesit-exceptions) | ||
| 1036 | eol)) | ||
| 1037 | @font-lock-type-face)) | ||
| 1038 | (type (identifier) @font-lock-type-face)))) | ||
| 902 | 1039 | ||
| 903 | ;;; Indentation | 1040 | ;;; Indentation |
| 904 | 1041 | ||
| @@ -5106,6 +5243,73 @@ To this: | |||
| 5106 | (python-imenu-format-parent-item-jump-label-function fn)) | 5243 | (python-imenu-format-parent-item-jump-label-function fn)) |
| 5107 | (python-imenu-create-index)))))) | 5244 | (python-imenu-create-index)))))) |
| 5108 | 5245 | ||
| 5246 | ;;; Tree-sitter imenu | ||
| 5247 | ;; | ||
| 5248 | ;; This works, but is slower than the native functions, presumably | ||
| 5249 | ;; because traversing the parser tree is slower than scanning the | ||
| 5250 | ;; text. Also I'm sure this consumes more memory as we allocate | ||
| 5251 | ;; memory for every node in the tree. | ||
| 5252 | |||
| 5253 | (defun python--imenu-treesit-create-index (&optional node) | ||
| 5254 | "Return tree Imenu alist for the current Python buffer. | ||
| 5255 | |||
| 5256 | Change `python-imenu-format-item-label-function', | ||
| 5257 | `python-imenu-format-parent-item-label-function', | ||
| 5258 | `python-imenu-format-parent-item-jump-label-function' to | ||
| 5259 | customize how labels are formatted. | ||
| 5260 | |||
| 5261 | NODE is the root node of the subtree you want to build an index | ||
| 5262 | of. If nil, use the root node of the whole parse tree. | ||
| 5263 | |||
| 5264 | Similar to `python-imenu-create-index' but use tree-sitter." | ||
| 5265 | (let* ((node (or node (treesit-buffer-root-node 'python))) | ||
| 5266 | (children (treesit-node-children node t)) | ||
| 5267 | (subtrees (mapcan #'python--imenu-treesit-create-index | ||
| 5268 | children)) | ||
| 5269 | (type (pcase (treesit-node-type node) | ||
| 5270 | ("function_definition" 'def) | ||
| 5271 | ("class_definition" 'class) | ||
| 5272 | (_ nil))) | ||
| 5273 | (name (when type | ||
| 5274 | (treesit-node-text | ||
| 5275 | (treesit-node-child-by-field-name | ||
| 5276 | node "name") t)))) | ||
| 5277 | (cond | ||
| 5278 | ;; 1. This node is a function/class and doesn't have children. | ||
| 5279 | ((and type (not subtrees)) | ||
| 5280 | (let ((label | ||
| 5281 | (funcall python-imenu-format-item-label-function | ||
| 5282 | type name))) | ||
| 5283 | (list (cons label | ||
| 5284 | (set-marker (make-marker) | ||
| 5285 | (treesit-node-start node)))))) | ||
| 5286 | ;; 2. This node is a function/class and has children. | ||
| 5287 | ((and type subtrees) | ||
| 5288 | (let ((parent-label | ||
| 5289 | (funcall python-imenu-format-parent-item-label-function | ||
| 5290 | type name)) | ||
| 5291 | (jump-label | ||
| 5292 | (funcall python-imenu-format-parent-item-jump-label-function | ||
| 5293 | type name))) | ||
| 5294 | `((,parent-label | ||
| 5295 | ,(cons jump-label (set-marker (make-marker) | ||
| 5296 | (treesit-node-start node))) | ||
| 5297 | ,@subtrees)))) | ||
| 5298 | ;; 3. This node is not a function/class. | ||
| 5299 | ((not type) subtrees)))) | ||
| 5300 | |||
| 5301 | (defun python--imenu-treesit-create-flat-index () | ||
| 5302 | "Return flat outline of the current Python buffer for Imenu. | ||
| 5303 | |||
| 5304 | Change `python-imenu-format-item-label-function', | ||
| 5305 | `python-imenu-format-parent-item-label-function', | ||
| 5306 | `python-imenu-format-parent-item-jump-label-function' to | ||
| 5307 | customize how labels are formatted. | ||
| 5308 | |||
| 5309 | Similar to `python-imenu-create-flat-index' but use | ||
| 5310 | tree-sitter." | ||
| 5311 | (python-imenu-create-flat-index | ||
| 5312 | (python--imenu-treesit-create-index))) | ||
| 5109 | 5313 | ||
| 5110 | ;;; Misc helpers | 5314 | ;;; Misc helpers |
| 5111 | 5315 | ||
| @@ -5171,6 +5375,29 @@ since it returns nil if point is not inside a defun." | |||
| 5171 | (concat (and type (format "%s " type)) | 5375 | (concat (and type (format "%s " type)) |
| 5172 | (mapconcat #'identity names "."))))))) | 5376 | (mapconcat #'identity names "."))))))) |
| 5173 | 5377 | ||
| 5378 | (defun python-info-treesit-current-defun (&optional include-type) | ||
| 5379 | "Identical to `python-info-current-defun' but use tree-sitter. | ||
| 5380 | For INCLUDE-TYPE see `python-info-current-defun'." | ||
| 5381 | (let ((node (treesit-node-at (point))) | ||
| 5382 | (name-list ()) | ||
| 5383 | (type 'def)) | ||
| 5384 | (cl-loop while node | ||
| 5385 | if (pcase (treesit-node-type node) | ||
| 5386 | ("function_definition" | ||
| 5387 | (setq type 'def)) | ||
| 5388 | ("class_definition" | ||
| 5389 | (setq type 'class)) | ||
| 5390 | (_ nil)) | ||
| 5391 | do (push (treesit-node-text | ||
| 5392 | (treesit-node-child-by-field-name node "name") | ||
| 5393 | t) | ||
| 5394 | name-list) | ||
| 5395 | do (setq node (treesit-node-parent node)) | ||
| 5396 | finally return (concat (if include-type | ||
| 5397 | (format "%s " type) | ||
| 5398 | "") | ||
| 5399 | (string-join name-list "."))))) | ||
| 5400 | |||
| 5174 | (defun python-info-current-symbol (&optional replace-self) | 5401 | (defun python-info-current-symbol (&optional replace-self) |
| 5175 | "Return current symbol using dotty syntax. | 5402 | "Return current symbol using dotty syntax. |
| 5176 | With optional argument REPLACE-SELF convert \"self\" to current | 5403 | With optional argument REPLACE-SELF convert \"self\" to current |
| @@ -5851,13 +6078,20 @@ REPORT-FN is Flymake's callback function." | |||
| 5851 | 6078 | ||
| 5852 | (setq-local forward-sexp-function python-forward-sexp-function) | 6079 | (setq-local forward-sexp-function python-forward-sexp-function) |
| 5853 | 6080 | ||
| 5854 | (setq-local font-lock-defaults | 6081 | (if (and python-use-tree-sitter |
| 6082 | (treesit-can-enable-p)) | ||
| 6083 | (progn | ||
| 6084 | (setq-local font-lock-defaults '(nil t)) | ||
| 6085 | (setq-local treesit-font-lock-settings | ||
| 6086 | python--treesit-settings) | ||
| 6087 | (treesit-font-lock-enable)) | ||
| 6088 | (setq-local font-lock-defaults | ||
| 5855 | `(,python-font-lock-keywords | 6089 | `(,python-font-lock-keywords |
| 5856 | nil nil nil nil | 6090 | nil nil nil nil |
| 5857 | (font-lock-syntactic-face-function | 6091 | (font-lock-syntactic-face-function |
| 5858 | . python-font-lock-syntactic-face-function) | 6092 | . python-font-lock-syntactic-face-function) |
| 5859 | (font-lock-extend-after-change-region-function | 6093 | (font-lock-extend-after-change-region-function |
| 5860 | . python-font-lock-extend-region))) | 6094 | . python-font-lock-extend-region)))) |
| 5861 | 6095 | ||
| 5862 | (setq-local syntax-propertize-function | 6096 | (setq-local syntax-propertize-function |
| 5863 | python-syntax-propertize-function) | 6097 | python-syntax-propertize-function) |
| @@ -5892,7 +6126,11 @@ REPORT-FN is Flymake's callback function." | |||
| 5892 | (setq-local add-log-current-defun-function | 6126 | (setq-local add-log-current-defun-function |
| 5893 | #'python-info-current-defun) | 6127 | #'python-info-current-defun) |
| 5894 | 6128 | ||
| 5895 | (add-hook 'which-func-functions #'python-info-current-defun nil t) | 6129 | (if (and python-use-tree-sitter |
| 6130 | (treesit-can-enable-p)) | ||
| 6131 | (add-hook 'which-func-functions | ||
| 6132 | #'python-info-treesit-current-defun nil t) | ||
| 6133 | (add-hook 'which-func-functions #'python-info-current-defun nil t)) | ||
| 5896 | 6134 | ||
| 5897 | (setq-local skeleton-further-elements | 6135 | (setq-local skeleton-further-elements |
| 5898 | '((abbrev-mode nil) | 6136 | '((abbrev-mode nil) |