diff options
-rw-r--r-- | doc/lispref/parsing.texi | 4 | ||||
-rw-r--r-- | lisp/progmodes/c-ts-mode.el | 48 | ||||
-rw-r--r-- | lisp/progmodes/js.el | 116 | ||||
-rw-r--r-- | src/treesit.c | 43 |
4 files changed, 131 insertions, 80 deletions
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index b7199f071bc..86b3bd54e7c 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -505,7 +505,9 @@ notification. Every time a parser reparses a buffer, it compares the old and new parse-tree, computes the ranges in which nodes have changed, and -passes the ranges to notifier functions. +passes the ranges to notifier functions. Note that the initial parse +is also considered a ``change'', so notifier functions are called on +the initial parse, with range being the whole buffer. @defun treesit-parser-add-notifier parser function This function adds @var{function} to @var{parser}'s list of diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 73e488a8058..12e021bc670 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -63,6 +63,8 @@ follows the form of `treesit-simple-indent-rules'." (function :tag "A function for user customized style" ignore)) :group 'c) +;;; Syntax table + (defvar c-ts-mode--syntax-table (let ((table (make-syntax-table))) ;; Taken from the cc-langs version @@ -85,13 +87,27 @@ follows the form of `treesit-simple-indent-rules'." table) "Syntax table for `c-ts-mode'.") -(defvar c++-ts-mode--syntax-table - (let ((table (make-syntax-table c-ts-mode--syntax-table))) - ;; Template delimiters. - (modify-syntax-entry ?< "(" table) - (modify-syntax-entry ?> ")" table) - table) - "Syntax table for `c++-ts-mode'.") +(defun c-ts-mode--syntax-propertize (beg end) + "Apply syntax text property to template delimiters between BEG and END. + +< and > are usually punctuation, e.g., in ->. But when used for +templates, they should be considered pairs. + +This function checks for < and > in the changed RANGES and apply +appropriate text property to alter the syntax of template +delimiters < and >'s." + (goto-char beg) + (while (re-search-forward (rx (or "<" ">")) end t) + (pcase (treesit-node-type + (treesit-node-parent + (treesit-node-at (match-beginning 0)))) + ("template_argument_list" + (put-text-property (match-beginning 0) + (match-end 0) + 'syntax-table + (pcase (char-before) + (?< '(4 . ?>)) + (?> '(5 . ?<)))))))) ;;; Indent @@ -574,6 +590,10 @@ ARG is passed to `fill-paragraph'." (goto-char (match-beginning 1)) (setq start-marker (point-marker)) (replace-match " " nil nil nil 1)) + ;; Include whitespaces before /*. + (goto-char start) + (beginning-of-line) + (setq start (point)) ;; Mask spaces before "*/" if it is attached at the end ;; of a sentence rather than on its own line. (goto-char end) @@ -645,11 +665,18 @@ Set up: (concat (rx (* (syntax whitespace)) (group (or (seq "/" (+ "/")) (* "*")))) adaptive-fill-regexp)) - ;; Same as `adaptive-fill-regexp'. + ;; Note the missing * comparing to `adaptive-fill-regexp'. The + ;; reason for its absence is a bit convoluted to explain. Suffice + ;; to say that without it, filling a single line paragraph that + ;; starts with /* doesn't insert * at the beginning of each + ;; following line, and filling a multi-line paragraph whose first + ;; two lines start with * does insert * at the beginning of each + ;; following line. If you know how does adaptive filling works, you + ;; know what I mean. (setq-local adaptive-fill-first-line-regexp (rx bos (seq (* (syntax whitespace)) - (group (or (seq "/" (+ "/")) (* "*"))) + (group (seq "/" (+ "/"))) (* (syntax whitespace))) eos)) ;; Same as `adaptive-fill-regexp'. @@ -751,7 +778,6 @@ Set up: (define-derived-mode c++-ts-mode c-ts-base-mode "C++" "Major mode for editing C++, powered by tree-sitter." :group 'c++ - :syntax-table c++-ts-mode--syntax-table (unless (treesit-ready-p 'cpp) (error "Tree-sitter for C++ isn't available")) @@ -761,6 +787,8 @@ Set up: "raw_string_literal"))) (treesit-parser-create 'cpp) + (setq-local syntax-propertize-function + #'c-ts-mode--syntax-propertize) (setq-local treesit-simple-indent-rules (c-ts-mode--set-indent-style 'cpp)) diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index 653038a09e3..79b7e74ec41 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el @@ -3479,36 +3479,35 @@ This function is intended for use in `after-change-functions'." (treesit-font-lock-rules :language 'javascript - :override t :feature 'comment - `((comment) @font-lock-comment-face) + '((comment) @font-lock-comment-face) :language 'javascript - :override t :feature 'constant - `(((identifier) @font-lock-constant-face + '(((identifier) @font-lock-constant-face (:match "^[A-Z_][A-Z_\\d]*$" @font-lock-constant-face)) [(true) (false) (null)] @font-lock-constant-face) :language 'javascript - :override t :feature 'keyword `([,@js--treesit-keywords] @font-lock-keyword-face [(this) (super)] @font-lock-keyword-face) :language 'javascript - :override t :feature 'string - `((regex pattern: (regex_pattern)) @font-lock-string-face - (string) @font-lock-string-face - (template_string) @js--fontify-template-string - (template_substitution ["${" "}"] @font-lock-builtin-face)) + '((regex pattern: (regex_pattern)) @font-lock-string-face + (string) @font-lock-string-face) :language 'javascript + :feature 'string-interpolation :override t - :feature 'declaration - `((function + '((template_string) @js--fontify-template-string + (template_substitution ["${" "}"] @font-lock-delimiter-face)) + + :language 'javascript + :feature 'definition + '((function name: (identifier) @font-lock-function-name-face) (class_declaration @@ -3535,24 +3534,10 @@ This function is intended for use in `after-change-functions'." value: (array (number) (function)))) :language 'javascript - :override t - :feature 'identifier - `((new_expression - constructor: (identifier) @font-lock-type-face) - - (for_in_statement - left: (identifier) @font-lock-variable-name-face) - - (arrow_function - parameter: (identifier) @font-lock-variable-name-face)) - - :language 'javascript - :override t :feature 'property - ;; This needs to be before function-name feature, because methods - ;; can be both property and function-name, and we want them in - ;; function-name face. - `((property_identifier) @font-lock-property-face + '(((property_identifier) @font-lock-property-face + (:pred js--treesit-property-not-function-p + @font-lock-property-face)) (pair value: (identifier) @font-lock-variable-name-face) @@ -3561,36 +3546,27 @@ This function is intended for use in `after-change-functions'." ((shorthand_property_identifier_pattern) @font-lock-property-face)) :language 'javascript - :override t - :feature 'expression - `((assignment_expression - left: [(identifier) @font-lock-function-name-face - (member_expression property: (property_identifier) - @font-lock-function-name-face)] - right: [(function) (arrow_function)]) - - (call_expression + :feature 'assignment + '((assignment_expression + left: (_) @js--treesit-fontify-assignment-lhs)) + + :language 'javascript + :feature 'function + '((call_expression function: [(identifier) @font-lock-function-name-face (member_expression property: (property_identifier) @font-lock-function-name-face)]) - - (assignment_expression - left: [(identifier) @font-lock-variable-name-face - (member_expression - property: (property_identifier) @font-lock-variable-name-face)])) - - :language 'javascript - :override t - :feature 'pattern - `((pair_pattern key: (property_identifier) @font-lock-variable-name-face) - (array_pattern (identifier) @font-lock-variable-name-face)) + (method_definition + name: (property_identifier) @font-lock-function-name-face) + (function_declaration + name: (identifier) @font-lock-function-name-face) + (function + name: (identifier) @font-lock-function-name-face)) :language 'javascript - :override t :feature 'jsx - `( - (jsx_opening_element + '((jsx_opening_element [(nested_identifier (identifier)) (identifier)] @font-lock-function-name-face) @@ -3608,7 +3584,7 @@ This function is intended for use in `after-change-functions'." :language 'javascript :feature 'number - `((number) @font-lock-number-face + '((number) @font-lock-number-face ((identifier) @font-lock-number-face (:match "^\\(:?NaN\\|Infinity\\)$" @font-lock-number-face))) @@ -3657,6 +3633,31 @@ OVERRIDE is the override flag described in (setq font-beg (treesit-node-end child) child (treesit-node-next-sibling child))))) +(defun js--treesit-property-not-function-p (node) + "Check that NODE, a property_identifier, is not used as a function." + (not (equal (treesit-node-type + (treesit-node-parent ; Maybe call_expression. + (treesit-node-parent ; Maybe member_expression. + node))) + "call_expression"))) + +(defvar js--treesit-lhs-identifier-query + (treesit-query-compile 'javascript '((identifier) @id + (property_identifier) @id)) + "Query that captures identifier and query_identifier.") + +(defun js--treesit-fontify-assignment-lhs (node override start end &rest _) + "Fontify the lhs NODE of an assignment_expression. +For OVERRIDE, START, END, see `treesit-font-lock-rules'." + (dolist (node (treesit-query-capture + node js--treesit-lhs-identifier-query nil nil t)) + (treesit-fontify-with-override + (treesit-node-start node) (treesit-node-end node) + (pcase (treesit-node-type node) + ("identifier" 'font-lock-variable-name-face) + ("property_identifier" 'font-lock-property-face)) + override start end))) + (defun js--treesit-defun-name (node) "Return the defun name of NODE. Return nil if there is no name or if NODE is not a defun node." @@ -3815,11 +3816,12 @@ Currently there are `js-mode' and `js-ts-mode'." ;; Fontification. (setq-local treesit-font-lock-settings js--treesit-font-lock-settings) (setq-local treesit-font-lock-feature-list - '(( comment declaration) + '(( comment definition) ( keyword string) - ( constant escape-sequence expression - identifier jsx number pattern property) - ( bracket delimiter operator))) + ( assignment constant escape-sequence jsx number + pattern) + ( bracket delimiter function operator property + string-interpolation))) ;; Imenu (setq-local treesit-simple-imenu-settings `(("Function" "\\`function_declaration\\'" nil nil) diff --git a/src/treesit.c b/src/treesit.c index 813d4222f98..6570ada1d92 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -933,11 +933,24 @@ static void treesit_call_after_change_functions (TSTree *old_tree, TSTree *new_tree, Lisp_Object parser) { - uint32_t len; - TSRange *ranges = ts_tree_get_changed_ranges (old_tree, new_tree, &len); + /* If the old_tree is NULL, meaning this is the first parse, the + changed range is the whole buffer. */ + Lisp_Object lisp_ranges; struct buffer *buf = XBUFFER (XTS_PARSER (parser)->buffer); - Lisp_Object lisp_ranges = treesit_make_ranges (ranges, len, buf); - xfree (ranges); + if (old_tree) + { + uint32_t len; + TSRange *ranges = ts_tree_get_changed_ranges (old_tree, new_tree, &len); + lisp_ranges = treesit_make_ranges (ranges, len, buf); + xfree (ranges); + } + else + { + struct buffer *oldbuf = current_buffer; + set_buffer_internal (buf); + lisp_ranges = Fcons (Fcons (Fpoint_min (), Fpoint_max ()), Qnil); + set_buffer_internal (oldbuf); + } specpdl_ref count = SPECPDL_INDEX (); @@ -955,6 +968,11 @@ treesit_call_after_change_functions (TSTree *old_tree, TSTree *new_tree, static void treesit_ensure_parsed (Lisp_Object parser) { + /* Make sure this comes before everything else, see comment + (ref:notifier-inside-ensure-parsed) for more detail. */ + if (!XTS_PARSER (parser)->need_reparse) + return; + struct buffer *buffer = XBUFFER (XTS_PARSER (parser)->buffer); /* Before we parse, catch up with the narrowing situation. */ @@ -963,8 +981,6 @@ treesit_ensure_parsed (Lisp_Object parser) because it might set the flag to true. */ treesit_sync_visible_region (parser); - if (!XTS_PARSER (parser)->need_reparse) - return; TSParser *treesit_parser = XTS_PARSER (parser)->parser; TSTree *tree = XTS_PARSER (parser)->tree; TSInput input = XTS_PARSER (parser)->input; @@ -984,14 +1000,17 @@ treesit_ensure_parsed (Lisp_Object parser) xsignal1 (Qtreesit_parse_error, buf); } - if (tree != NULL) - { - treesit_call_after_change_functions (tree, new_tree, parser); - ts_tree_delete (tree); - } - XTS_PARSER (parser)->tree = new_tree; XTS_PARSER (parser)->need_reparse = false; + + /* After-change functions should run at the very end, most crucially + after need_reparse is set to false, this way if the function + calls some tree-sitter function which invokes + treesit_ensure_parsed again, it returns early and do not + recursively call the after change functions again. + (ref:notifier-inside-ensure-parsed) */ + treesit_call_after_change_functions (tree, new_tree, parser); + ts_tree_delete (tree); } /* This is the read function provided to tree-sitter to read from a |