aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/progmodes/python.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/progmodes/python.el')
-rw-r--r--lisp/progmodes/python.el246
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.
272Currently `python-mode' uses tree-sitter for font-locking, imenu,
273and 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.
900It makes underscores and dots word constituent chars.") 907It 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.
977Do 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
5256Change `python-imenu-format-item-label-function',
5257`python-imenu-format-parent-item-label-function',
5258`python-imenu-format-parent-item-jump-label-function' to
5259customize how labels are formatted.
5260
5261NODE is the root node of the subtree you want to build an index
5262of. If nil, use the root node of the whole parse tree.
5263
5264Similar 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
5304Change `python-imenu-format-item-label-function',
5305`python-imenu-format-parent-item-label-function',
5306`python-imenu-format-parent-item-jump-label-function' to
5307customize how labels are formatted.
5308
5309Similar to `python-imenu-create-flat-index' but use
5310tree-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.
5380For 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.
5176With optional argument REPLACE-SELF convert \"self\" to current 5403With 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)