commit de98b5a24f2b2ad293b4298bb898842b8f23e5bc (HEAD, refs/remotes/origin/master) Author: Yuan Fu Date: Sun Dec 1 16:47:53 2024 -0800 ; Indent by 8 in BSD indent tests for c-ts-mode * test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts: Set indent offset to 8. diff --git a/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts b/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts index 0f6f87800ec..2325b3b008d 100644 --- a/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts +++ b/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts @@ -2,7 +2,7 @@ Code: (lambda () (c-ts-mode) (setq-local indent-tabs-mode nil) - (setq-local c-ts-mode-indent-offset 2) + (setq-local c-ts-mode-indent-offset 8) (c-ts-mode-set-style 'bsd) (indent-region (point-min) (point-max))) @@ -14,7 +14,7 @@ Name: Basic int main (void) { - return 0; + return 0; } =-=-= @@ -24,10 +24,10 @@ Name: Hanging Braces int main (void) { - if (true) - { - | - } + if (true) + { + | + } } =-=-= @@ -38,20 +38,20 @@ int main (void) { label: - return 0; - if (true) - { - label: - return 0; - } - else - { - if (true) - { - label: - return 0; - } - } + return 0; + if (true) + { + label: + return 0; + } + else + { + if (true) + { + label: + return 0; + } + } } =-=-= @@ -60,14 +60,14 @@ Name: If-Else =-= int main() { - if (true) - { - return 0; - } - else - { - return 1; - } + if (true) + { + return 0; + } + else + { + return 1; + } } =-=-= @@ -75,7 +75,7 @@ Name: Empty Line =-= int main() { - | + | } =-=-= @@ -86,9 +86,9 @@ int main (int argc, char *argv[]) { - { - int i = 0; - } + { + int i = 0; + } } =-=-= @@ -111,17 +111,17 @@ else break; =-= for (int i = 0; i < 5; i++) - continue; + continue; while (true) - return 1; + return 1; do - i++; + i++; while (true) if (true) - break; + break; else - break; + break; =-=-= commit 9acf6eff01a82c3fe9ca8897f38b76dbb6079648 Author: Yuan Fu Date: Sun Dec 1 16:40:05 2024 -0800 Standardize and promote c-ts-mode's custom matcher and anchor Specifically, standalone-parent and prev-sibling. The c-ts-mode custom version skips labels and proproc directives. * lisp/progmodes/c-ts-mode.el: (c-ts-mode--standalone-parent-skip-preproc): Rename to c-ts-mode--standalone-parent, and make it skip labels too. (c-ts-mode--preproc-indent-rules): Rename standalone-parent and prev-sibling. (c-ts-mode--label-indent-rules): Use custom standalone-parent and prev-sibling. diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 54617e20eba..6699e4ece48 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -281,13 +281,13 @@ is actually the parent of point at the moment of indentation." 0 c-ts-mode-indent-offset))) -(defun c-ts-mode--anchor-prev-sibling (node parent bol &rest _) +(defun c-ts-mode--prev-sibling (node parent bol &rest _) "Return the start of the previous named sibling of NODE. -This anchor handles the special case where the previous sibling -is a labeled_statement; in that case, return the child of the -labeled statement instead. (Actually, recursively go down until -the node isn't a labeled_statement.) E.g., +This anchor handles the special case where the previous sibling is a +labeled_statement or preproc directive; in that case, return the child +of the labeled statement instead. (Actually, recursively go down until +the node isn't a labeled_statement or preproc.) E.g., label: int x = 1; @@ -340,8 +340,8 @@ characters of the current line." ;; prev-sibling doesn't have a child. (treesit-node-start prev-sibling))) -(defun c-ts-mode--standalone-parent-skip-preproc (_n parent &rest _) - "Like the standalone-parent anchor but skips preproc nodes. +(defun c-ts-mode--standalone-parent (_n parent &rest _) + "Like the standalone-parent anchor but skips preproc nodes and labels. PARENT is the parent of the current node." (save-excursion (treesit-node-start @@ -350,7 +350,8 @@ PARENT is the parent of the current node." ;; nil. parent (lambda (node) (and node - (not (string-search "preproc" (treesit-node-type node))) + (not (treesit-node-match-p + node (rx (or "preproc" "labeled_statement")))) (progn (goto-char (treesit-node-start node)) (looking-back (rx bol (* whitespace)) @@ -381,24 +382,24 @@ NODE and PARENT as usual." `(((node-is "preproc") column-0 0) ((node-is "#endif") column-0 0) ((match "preproc_call" "compound_statement") column-0 0) - ((prev-line-is "#endif") c-ts-mode--anchor-prev-sibling 0) + ((prev-line-is "#endif") c-ts-mode--prev-sibling 0) ;; Top-level things under a preproc directive. Note that ;; "preproc" matches more than one type: it matches ;; preproc_if, preproc_elif, etc. ((n-p-gp nil "preproc" "translation_unit") column-0 0) ;; Indent rule for an empty line after a preproc directive. ((and no-node (parent-is ,(rx (or "\n" "preproc")))) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode--preproc-offset) + c-ts-mode--standalone-parent c-ts-mode--preproc-offset) ;; Statement under a preproc directive, the first statement ;; indents against parent, the rest statements indent to ;; their prev-sibling. ((match nil ,(rx "preproc_" (or "if" "elif")) nil 3 3) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) + c-ts-mode--standalone-parent c-ts-mode-indent-offset) ((match nil "preproc_ifdef" nil 2 2) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) + c-ts-mode--standalone-parent c-ts-mode-indent-offset) ((match nil "preproc_else" nil 1 1) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) - ((parent-is "preproc") c-ts-mode--anchor-prev-sibling 0)) + c-ts-mode--standalone-parent c-ts-mode-indent-offset) + ((parent-is "preproc") c-ts-mode--prev-sibling 0)) "Indent rules for preprocessors.") (defun c-ts-mode--macro-heuristic-rules (node parent &rest _) @@ -554,24 +555,18 @@ NODE, PARENT, BOL, ARGS are as usual." (cons (pos-bol) 1)) ;; Indent the label itself. ((treesit-node-match-p node "labeled_statement") - (cons (apply (alist-get 'standalone-parent - treesit-simple-indent-presets) - node parent bol args) + (cons (c-ts-mode--standalone-parent node parent bol args) 0)) ;; Indent the statement below the label. ((treesit-node-match-p parent "labeled_statement") - (cons (apply (alist-get 'standalone-parent - treesit-simple-indent-presets) - parent (treesit-node-parent parent) bol args) + (cons (c-ts-mode--standalone-parent node parent bol args) c-ts-mode-indent-offset)) ;; If previous sibling is a labeled_statement, align to it's ;; children, which is the previous statement. ((and (not (treesit-node-match-p node "}")) (treesit-node-match-p (treesit-node-prev-sibling node) "labeled_statement")) - (cons (treesit-node-start - (treesit-node-child - (treesit-node-prev-sibling node) 1 'named)) + (cons (c-ts-mode--prev-sibling node parent bol args) 0)) (t nil))) commit 994258f55675baa8473e6cff9aab7ecb31338580 Author: Yuan Fu Date: Sun Dec 1 16:19:01 2024 -0800 Remove unused functions in c-ts-mode Remove functions that became unused due to the previous change. * lisp/progmodes/c-ts-mode.el: (c-ts-mode--standalone-grandparent): (c-ts-mode--else-heuristic): (c-ts-mode--first-sibling): (c-ts-mode--parent-is-not-top-compound): (c-ts-mode--indent-styles): (c-ts-mode--top-level-label-matcher): Remove functions. (c-ts-mode--simple-indent-rules): Move two label rules into c-ts-mode--label-indent-rules. (c-ts-mode--label-indent-rules): Add rules diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 3059898f6d5..54617e20eba 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -357,42 +357,6 @@ PARENT is the parent of the current node." (line-beginning-position))))) t)))) -(defun c-ts-mode--standalone-grandparent (_node parent bol &rest args) - "Like the standalone-parent anchor but pass it the grandparent. -NODE, PARENT, BOL, ARGS are the same as in other anchors." - (apply (alist-get 'standalone-parent treesit-simple-indent-presets) - parent (treesit-node-parent parent) bol args)) - -(defun c-ts-mode--else-heuristic (node parent bol &rest _) - "Heuristic matcher for when \"else\" is followed by a closing bracket. -PARENT is NODE's parent, BOL is the beginning of non-whitespace -characters of the current line." - (and (null node) - (save-excursion - (forward-line -1) - (looking-at (rx (* whitespace) "else" (* whitespace) eol))) - (let ((next-node (treesit-node-first-child-for-pos parent bol))) - (equal (treesit-node-type next-node) "}")))) - -(defun c-ts-mode--first-sibling (node parent &rest _) - "Matches when NODE is the \"first sibling\". -\"First sibling\" is defined as: the first child node of PARENT -such that it's on its own line. NODE is the node to match and -PARENT is its parent." - (let ((prev-sibling (treesit-node-prev-sibling node t))) - (or (null prev-sibling) - (save-excursion - (goto-char (treesit-node-start prev-sibling)) - (<= (line-beginning-position) - (treesit-node-start parent) - (line-end-position)))))) - -(defun c-ts-mode--parent-is-not-top-compound (_n parent &rest _) - "Matches when PARENT is not the top level compound statement. -The top-level compound is the {} that immediately follows the function -signature." - (not (equal "function_definition" (treesit-node-type (treesit-node-parent parent))))) - (defun c-ts-mode--for-loop-indent-rule (node parent &rest _) "Indentation rule for the for-loop. @@ -519,11 +483,6 @@ MODE can be `c' or `c++'. STYLE can be `gnu', `k&r', `linux', `bsd'." 1) ((parent-is "comment") prev-adaptive-prefix 0) - ;; Labels. - ((node-is "labeled_statement") standalone-parent 0) - ((parent-is "labeled_statement") - c-ts-mode--standalone-grandparent c-ts-mode-indent-offset) - ;; Preproc directives ((node-is "preproc_arg") no-indent) ((node-is "preproc") column-0 0) @@ -554,155 +513,6 @@ MODE can be `c' or `c++'. STYLE can be `gnu', `k&r', `linux', `bsd'." ('c `((c . ,rules))) ('c++ `((cpp . ,rules)))))) -(defun c-ts-mode--indent-styles (mode) - "Indent rules supported by `c-ts-mode'. -MODE is either `c' or `cpp'." - (let ((common - `((c-ts-mode--for-each-tail-body-matcher prev-line c-ts-mode-indent-offset) - ;; If the user types "else" and hits RET, they expect point - ;; on the empty line to be indented; this rule does that. - ;; This heuristic is intentionally very specific because - ;; more general heuristic is very error-prone, see - ;; discussion in bug#67417. - (c-ts-mode--else-heuristic prev-line c-ts-mode-indent-offset) - - ((parent-is "translation_unit") column-0 0) - ((query "(ERROR (ERROR)) @indent") column-0 0) - ((node-is ")") parent 1) - ((node-is "]") parent-bol 0) - ((node-is "else") parent-bol 0) - ((node-is "case") parent-bol 0) - ((node-is "preproc_arg") no-indent) - ;; `c-ts-common-looking-at-star' has to come before - ;; `c-ts-common-comment-2nd-line-matcher'. - ((and (parent-is "comment") c-ts-common-looking-at-star) - c-ts-common-comment-start-after-first-star -1) - (c-ts-common-comment-2nd-line-matcher - c-ts-common-comment-2nd-line-anchor - 1) - ((parent-is "comment") prev-adaptive-prefix 0) - - ;; Labels. - ((node-is "labeled_statement") standalone-parent 0) - ((parent-is "labeled_statement") - c-ts-mode--standalone-grandparent c-ts-mode-indent-offset) - - ;; Preproc directives - ((node-is "preproc") column-0 0) - ((node-is "#endif") column-0 0) - ((match "preproc_call" "compound_statement") column-0 0) - - ;; Top-level things under a preproc directive. Note that - ;; "preproc" matches more than one type: it matches - ;; preproc_if, preproc_elif, etc. - ((n-p-gp nil "preproc" "translation_unit") column-0 0) - ;; Indent rule for an empty line after a preproc directive. - ((and no-node (parent-is ,(rx (or "\n" "preproc")))) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode--preproc-offset) - ;; Statement under a preproc directive, the first statement - ;; indents against parent, the rest statements indent to - ;; their prev-sibling. - ((match nil ,(rx "preproc_" (or "if" "elif")) nil 3 3) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) - ((match nil "preproc_ifdef" nil 2 2) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) - ((match nil "preproc_else" nil 1 1) - c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) - ((parent-is "preproc") c-ts-mode--anchor-prev-sibling 0) - - ((parent-is "function_definition") parent-bol 0) - ((parent-is "pointer_declarator") parent-bol 0) - ((parent-is ,(rx bos "declaration" eos)) parent-bol 0) - ((parent-is "conditional_expression") first-sibling 0) - ((parent-is "assignment_expression") parent-bol c-ts-mode-indent-offset) - ((parent-is "concatenated_string") first-sibling 0) - ((parent-is "comma_expression") first-sibling 0) - ((parent-is "init_declarator") parent-bol c-ts-mode-indent-offset) - ((parent-is "parenthesized_expression") first-sibling 1) - ((parent-is "argument_list") first-sibling 1) - ((parent-is "parameter_list") first-sibling 1) - ((parent-is "binary_expression") parent 0) - ((query "(for_statement initializer: (_) @indent)") parent-bol 5) - ((query "(for_statement condition: (_) @indent)") parent-bol 5) - ((query "(for_statement update: (_) @indent)") parent-bol 5) - ((query "(call_expression arguments: (_) @indent)") parent c-ts-mode-indent-offset) - ((parent-is "call_expression") parent 0) - ;; Closing bracket. This should be before initializer_list - ;; (and probably others) rule because that rule (and other - ;; similar rules) will match the closing bracket. (Bug#61398) - ((and (node-is "}") c-ts-mode--parent-is-not-top-compound) parent-bol 0) - ((node-is "}") standalone-parent 0) - ,@(when (eq mode 'cpp) - '(((node-is "access_specifier") parent-bol 0) - ;; Indent the body of namespace definitions. - ((parent-is "declaration_list") parent-bol c-ts-mode-indent-offset) - ((parent-is "template_declaration") parent-bol 0))) - - - ;; int[5] a = { 0, 0, 0, 0 }; - ((match nil "initializer_list" nil 1 1) parent-bol c-ts-mode-indent-offset) - ((parent-is "initializer_list") c-ts-mode--anchor-prev-sibling 0) - ;; Statement in enum. - ((match nil "enumerator_list" nil 1 1) standalone-parent c-ts-mode-indent-offset) - ((parent-is "enumerator_list") c-ts-mode--anchor-prev-sibling 0) - ;; Statement in struct and union. - ((match nil "field_declaration_list" nil 1 1) standalone-parent c-ts-mode-indent-offset) - ((parent-is "field_declaration_list") c-ts-mode--anchor-prev-sibling 0) - - ;; Statement in {} blocks. - ((and (parent-is "compound_statement") c-ts-mode--parent-is-not-top-compound) - parent-bol c-ts-mode-indent-offset) - ((or (and (parent-is "compound_statement") - ;; If the previous sibling(s) are not on their - ;; own line, indent as if this node is the first - ;; sibling (Bug#67357) - c-ts-mode--first-sibling) - (match null "compound_statement")) - standalone-parent c-ts-mode-indent-offset) - ((parent-is "compound_statement") c-ts-mode--anchor-prev-sibling 0) - ;; Opening bracket. - ((node-is "compound_statement") standalone-parent c-ts-mode-indent-offset) - ;; Bug#61291. - ((match "expression_statement" nil "body") standalone-parent c-ts-mode-indent-offset) - ;; These rules are for cases where the body is bracketless. - ;; Tested by the "Bracketless Simple Statement" test. - ((parent-is "if_statement") standalone-parent c-ts-mode-indent-offset) - ((parent-is "else_clause") standalone-parent c-ts-mode-indent-offset) - ((parent-is "for_statement") standalone-parent c-ts-mode-indent-offset) - ((match "while" "do_statement") parent-bol 0) ; (do_statement "while") - ((parent-is "while_statement") standalone-parent c-ts-mode-indent-offset) - ((parent-is "do_statement") standalone-parent c-ts-mode-indent-offset) - - ((parent-is "case_statement") standalone-parent c-ts-mode-indent-offset) - - ,@(when (eq mode 'cpp) - `(((node-is "field_initializer_list") parent-bol ,(* c-ts-mode-indent-offset 2))))))) - `((gnu - ;; Prepend rules to set highest priority - ((match "while" "do_statement") parent 0) - (c-ts-mode--top-level-label-matcher column-0 1) - ,@common) - (k&r ,@common) - (linux - ;; Reference: - ;; https://www.kernel.org/doc/html/latest/process/coding-style.html, - ;; and script/Lindent in Linux kernel repository. - ((node-is "labeled_statement") column-0 0) - ,@common) - (bsd - ((node-is "}") parent-bol 0) - ((node-is "labeled_statement") parent-bol c-ts-mode-indent-offset) - ((parent-is "labeled_statement") parent-bol c-ts-mode-indent-offset) - ((parent-is "compound_statement") parent-bol c-ts-mode-indent-offset) - ((match "compound_statement" "if_statement") standalone-parent 0) - ((match "compound_statement" "else_clause") standalone-parent 0) - ((match "compound_statement" "for_statement") standalone-parent 0) - ((match "compound_statement" "while_statement") standalone-parent 0) - ((match "compound_statement" "switch_statement") standalone-parent 0) - ((match "compound_statement" "case_statement") standalone-parent 0) - ((match "compound_statement" "do_statement") standalone-parent 0) - ,@common)))) - (defun c-ts-mode--parenthesized-expression-indent-rule (_node parent &rest _) "Indent rule that indents aprenthesized expression. @@ -732,9 +542,9 @@ NODE, PARENT are the same as other indent rules." (treesit-node-child parent 0 'named)) 0)))) -(defun c-ts-mode--label-indent-rules (node parent &rest _) +(defun c-ts-mode--label-indent-rules (node parent bol &rest args) "Handles indentation around labels. -NODE, PARENT are as usual." +NODE, PARENT, BOL, ARGS are as usual." (cond ;; Matches the top-level labels for GNU-style. ((and (eq c-ts-mode-indent-style 'gnu) @@ -742,6 +552,18 @@ NODE, PARENT are as usual." (treesit-node-match-p (treesit-node-parent parent) "function_definition")) (cons (pos-bol) 1)) + ;; Indent the label itself. + ((treesit-node-match-p node "labeled_statement") + (cons (apply (alist-get 'standalone-parent + treesit-simple-indent-presets) + node parent bol args) + 0)) + ;; Indent the statement below the label. + ((treesit-node-match-p parent "labeled_statement") + (cons (apply (alist-get 'standalone-parent + treesit-simple-indent-presets) + parent (treesit-node-parent parent) bol args) + c-ts-mode-indent-offset)) ;; If previous sibling is a labeled_statement, align to it's ;; children, which is the previous statement. ((and (not (treesit-node-match-p node "}")) @@ -753,14 +575,6 @@ NODE, PARENT are as usual." 0)) (t nil))) -(defun c-ts-mode--top-level-label-matcher (node parent &rest _) - "A matcher that matches a top-level label. -NODE should be a labeled_statement. PARENT is its parent." - (and (equal (treesit-node-type node) - "labeled_statement") - (equal "function_definition" - (treesit-node-type (treesit-node-parent parent))))) - ;;; Font-lock (defvar c-ts-mode--feature-list @@ -991,17 +805,17 @@ MODE is either `c' or `cpp'." :feature 'delimiter '((["," ":" ";"]) @font-lock-delimiter-face) -:language mode -:feature 'emacs-devel -:override t -`(((call_expression - (call_expression function: (identifier) @fn) - @c-ts-mode--fontify-DEFUN) - (:match "\\`DEFUN\\'" @fn)) - - ((function_definition type: (_) @for-each-tail) - @c-ts-mode--fontify-for-each-tail - (:match ,c-ts-mode--for-each-tail-regexp @for-each-tail))))) + :language mode + :feature 'emacs-devel + :override t + `(((call_expression + (call_expression function: (identifier) @fn) + @c-ts-mode--fontify-DEFUN) + (:match "\\`DEFUN\\'" @fn)) + + ((function_definition type: (_) @for-each-tail) + @c-ts-mode--fontify-for-each-tail + (:match ,c-ts-mode--for-each-tail-regexp @for-each-tail))))) ;;; Font-lock helpers commit 44fcd37a486399be048fb03b9456497af78632fe Author: Yuan Fu Date: Sat Nov 30 23:49:14 2024 -0800 Add more c-ts-mode indent tests * test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts: Fix label test. * test/lisp/progmodes/c-ts-mode-resources/indent.erts: Add some test, make other tests harder. diff --git a/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts b/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts index fa65ba83a69..0f6f87800ec 100644 --- a/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts +++ b/test/lisp/progmodes/c-ts-mode-resources/indent-bsd.erts @@ -37,19 +37,19 @@ Name: Labels int main (void) { - label: - return 0; +label: + return 0; if (true) { - label: - return 0; + label: + return 0; } else { if (true) { - label: - return 0; + label: + return 0; } } } diff --git a/test/lisp/progmodes/c-ts-mode-resources/indent.erts b/test/lisp/progmodes/c-ts-mode-resources/indent.erts index 61e61677ed7..691f5b6ecfd 100644 --- a/test/lisp/progmodes/c-ts-mode-resources/indent.erts +++ b/test/lisp/progmodes/c-ts-mode-resources/indent.erts @@ -54,14 +54,27 @@ main (void) } =-=-= +Name: Enum +=-= +enum +week +{ + Mon, Tue, Wed, + Thur, Fri, Sat, Sun +}; +=-=-= + + Name: For Loop with Multi-line Condition (GNU Style) =-= int main() { - for (int i = 0; + for ( + int i = 0; i < b; - i++) + i++ + ) { return 0; } @@ -127,6 +140,15 @@ int main() { } =-=-= +Name: Type and function name on separate line +=-= +struct +aaa * +fn() +{ +}; +=-=-= + Name: Multiline Parameter List (bug#60398) =-= @@ -223,7 +245,7 @@ make_pair(int long_identifier_a[], int long_identifier_b[], =-=-= -Name: Compound Statement after code +Name: Compound Statement after code (Bug#74507) =-= #define IOTA(var, n) for (int var = 0; var != (n); ++var) @@ -233,11 +255,20 @@ IOTA (v, 10) { printf("%d ", v); } -const char *msg = "Hello, world!"; { -puts("Hello, world!"); +for (int i = 0; +i < 10; +i++) { +IOTA (v, 10) { +printf("%d ", v); } } +{ +IOTA (v, 10) { +printf("%d ", v); +} +} +} =-= #define IOTA(var, n) for (int var = 0; var != (n); ++var) int main() @@ -246,11 +277,20 @@ int main() printf("%d ", v); } - const char *msg = "Hello, world!"; { - puts("Hello, world!"); + for (int i = 0; + i < 10; + i++) { + IOTA (v, 10) { + printf("%d ", v); + } } -} + { + IOTA (v, 10) { + printf("%d ", v); + } + } +} =-=-= Name: Switch-Case statement @@ -503,6 +543,16 @@ namespace test { } =-=-= +Name: Access specifier +=-= +class MyClass { +public: // Public access specifier + int x; // Public attribute +private: // Private access specifier + int y; // Private attribute +}; +=-=-= + Name: Namespace and template (bug#72263) =-= @@ -510,11 +560,13 @@ namespace A { T myfunction1(const char *fname) { +return a; } template T myfunction2(const char *fname) { +return a; } } =-= @@ -522,11 +574,13 @@ namespace A { T myfunction1(const char *fname) { + return a; } template T myfunction2(const char *fname) { + return a; } } =-=-= commit 63d69bd154987bcc0434e0f85e09bf5dfa07b827 Author: Yuan Fu Date: Sun Dec 1 00:15:08 2024 -0800 Use new baseline indent rule in c-ts-mode Rework the indent rules in c-ts-mode using c-ts-common-baseline-indent-rule. Also reworked internal functions for setting indent styles to make it simpler and (more importantly) more readable. Now there's a single function, c-ts-mode--simple-indent-rules, that returns a value that can go straight to treesit-simple-indent-rules. * lisp/progmodes/c-ts-mode.el: (c-ts-mode--indent-style-setter): Simplify. (c-ts-mode--get-indent-style): Remove function. (c-ts-mode--prompt-for-style): Use a hard-coded list of styles. This is a tiny downgrade from the computed lists but is acceptable. (c-ts-mode-set-style): Use c-ts-mode--simple-indent-rules. (c-ts-mode--standalone-grandparent): Docstring change. (c-ts-mode--for-loop-indent-rule): New function. (c-ts-mode--preproc-indent-rules): New variable. Copied from old indent rules and added some new rule. (c-ts-mode--macro-heuristic-rules): (c-ts-mode--simple-indent-rules): (c-ts-mode--parenthesized-expression-indent-rule): (c-ts-mode--label-indent-rules): New functions. (c-ts-mode): (c++-ts-mode): Use c-ts-mode--simple-indent-rules. diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 59b34ef6b8b..3059898f6d5 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -130,18 +130,11 @@ the value of SYM in `c-ts-mode' and `c++-ts-mode' buffers to VAL. SYM should be `c-ts-mode-indent-style', and VAL should be a style symbol." (set-default sym val) - (named-let loop ((res nil) - (buffers (buffer-list))) - (if (null buffers) - (mapc (lambda (b) - (with-current-buffer b - (c-ts-mode-set-style val))) - res) - (let ((buffer (car buffers))) - (with-current-buffer buffer - (if (derived-mode-p '(c-ts-mode c++-ts-mode)) - (loop (append res (list buffer)) (cdr buffers)) - (loop res (cdr buffers)))))))) + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (derived-mode-p '(c-ts-mode c++-ts-mode)) + (setq-local c-ts-mode-indent-style val) + (c-ts-mode-set-style val))))) (defun c-ts-indent-style-safep (style) "Non-nil if STYLE's value is safe for file-local variables." @@ -165,23 +158,13 @@ the list of RULEs doesn't need to contain the language symbol." :safe 'c-ts-indent-style-safep :group 'c) -(defun c-ts-mode--get-indent-style (mode) - "Helper function to set indentation style. -MODE is either `c' or `cpp'." - (let ((style - (if (functionp c-ts-mode-indent-style) - (funcall c-ts-mode-indent-style) - (alist-get c-ts-mode-indent-style (c-ts-mode--indent-styles mode))))) - `((,mode ,@style)))) - (defun c-ts-mode--prompt-for-style () "Prompt for an indent style and return the symbol for it." - (let ((mode (if (derived-mode-p 'c-ts-mode) 'c 'c++))) - (intern - (completing-read - "Style: " - (mapcar #'car (c-ts-mode--indent-styles mode)) - nil t nil nil "gnu")))) + (intern + (completing-read + "Style: " + '(gnu k&r linux bsd) + nil t nil nil "gnu"))) (defun c-ts-mode-set-global-style (style) "Set the indent style of C/C++ modes globally to STYLE. @@ -202,9 +185,11 @@ To set the default indent style globally, use (user-error "The current buffer is not in `c-ts-mode' nor `c++-ts-mode'") (setq-local c-ts-mode-indent-style style) (setq treesit-simple-indent-rules - (treesit--indent-rules-optimize - (c-ts-mode--get-indent-style - (if (derived-mode-p 'c-ts-mode) 'c 'cpp)))))) + (if (functionp style) + (funcall style) + (c-ts-mode--simple-indent-rules + (if (derived-mode-p 'c-ts-mode) 'c 'c++) + style))))) (defcustom c-ts-mode-emacs-sources-support t "Whether to enable Emacs source-specific C features. @@ -374,8 +359,7 @@ PARENT is the parent of the current node." (defun c-ts-mode--standalone-grandparent (_node parent bol &rest args) "Like the standalone-parent anchor but pass it the grandparent. -PARENT is NODE's parent, BOL is the beginning of non-whitespace -characters of the current line." +NODE, PARENT, BOL, ARGS are the same as in other anchors." (apply (alist-get 'standalone-parent treesit-simple-indent-presets) parent (treesit-node-parent parent) bol args)) @@ -409,6 +393,167 @@ The top-level compound is the {} that immediately follows the function signature." (not (equal "function_definition" (treesit-node-type (treesit-node-parent parent))))) +(defun c-ts-mode--for-loop-indent-rule (node parent &rest _) + "Indentation rule for the for-loop. + +NODE and PARENT as usual." + (when (treesit-node-match-p parent "for_statement") + (pcase (treesit-node-field-name node) + ("initializer" + ;; Anchor is the opening paren. + (cons (treesit-node-start (treesit-node-child parent 1)) 1)) + ((or "condition" "update") + (cons (treesit-node-start (treesit-node-prev-sibling node 'named)) + 0)) + ("body" + (cons (c-ts-common--standalone-parent parent) + c-ts-mode-indent-offset)) + (_ (if (treesit-node-match-p node ")") + ;; Anchor is the opening paren. + (cons (treesit-node-start (treesit-node-child parent 1)) 0) + nil))))) + +(defvar c-ts-mode--preproc-indent-rules + `(((node-is "preproc") column-0 0) + ((node-is "#endif") column-0 0) + ((match "preproc_call" "compound_statement") column-0 0) + ((prev-line-is "#endif") c-ts-mode--anchor-prev-sibling 0) + ;; Top-level things under a preproc directive. Note that + ;; "preproc" matches more than one type: it matches + ;; preproc_if, preproc_elif, etc. + ((n-p-gp nil "preproc" "translation_unit") column-0 0) + ;; Indent rule for an empty line after a preproc directive. + ((and no-node (parent-is ,(rx (or "\n" "preproc")))) + c-ts-mode--standalone-parent-skip-preproc c-ts-mode--preproc-offset) + ;; Statement under a preproc directive, the first statement + ;; indents against parent, the rest statements indent to + ;; their prev-sibling. + ((match nil ,(rx "preproc_" (or "if" "elif")) nil 3 3) + c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) + ((match nil "preproc_ifdef" nil 2 2) + c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) + ((match nil "preproc_else" nil 1 1) + c-ts-mode--standalone-parent-skip-preproc c-ts-mode-indent-offset) + ((parent-is "preproc") c-ts-mode--anchor-prev-sibling 0)) + "Indent rules for preprocessors.") + +(defun c-ts-mode--macro-heuristic-rules (node parent &rest _) + "Heuristic indent rule for control flow macros. + +Eg, + + #define IOTA(var, n) for (int var = 0; var != (n); ++var) + + int main() + { + IOTA (v, 10) { + printf(\"%d \", v); <-- Here we want to indent + counter++; <-- Use baseline rule to align + } to prev sibling + +Checked by \"Compound Statement after code (Bug#74507)\" test. + +NODE and PARENT are the same as other indent rules." + (when (and (treesit-node-match-p parent "compound_statement") + (treesit-node-match-p (treesit-node-prev-sibling parent) + "expression_statement")) + (let ((parent-bol + (lambda () (save-excursion + (goto-char (treesit-node-start parent)) + (back-to-indentation) + (point))))) + (cond + ;; Closing brace. + ((treesit-node-match-p node "}") + (cons (funcall parent-bol) 0)) + ;; First sibling. + ((treesit-node-eq (treesit-node-child parent 0 'named) node) + (cons (funcall parent-bol) + c-ts-mode-indent-offset)))))) + +(defun c-ts-mode--simple-indent-rules (mode style) + "Return the indent rules for MODE and STYLE. + +The returned value can be set to `treesit-simple-indent-rules'. +MODE can be `c' or `c++'. STYLE can be `gnu', `k&r', `linux', `bsd'." + (let ((rules + `((c-ts-mode--for-each-tail-body-matcher + prev-line c-ts-mode-indent-offset) + + ;; Misc overrides. + ((parent-is "translation_unit") column-0 0) + ((node-is ,(rx (or "else" "case"))) standalone-parent 0) + ;; Align the while keyword to the do keyword. + ((match "while" "do_statement") parent 0) + c-ts-mode--parenthesized-expression-indent-rule + ;; Thanks to tree-sitter-c's weird for-loop grammar, we can't + ;; use the baseline indent rule for it. + c-ts-mode--for-loop-indent-rule + c-ts-mode--label-indent-rules + ,@c-ts-mode--preproc-indent-rules + c-ts-mode--macro-heuristic-rules + + ;; Make sure type and function definition components align and + ;; don't indent. Also takes care of GNU style opening braces. + ((parent-is ,(rx (or "function_definition" + "struct_specifier" + "enum_specifier" + "function_declarator" + "template_declaration"))) + parent 0) + ;; This is for the trailing-star stype: int * + ;; func() + ((match "function_declarator" nil "declarator") parent-bol 0) + ;; ((match nil "function_definition" "declarator") parent 0) + ;; ((match nil "struct_specifier" "name") parent 0) + ;; ((match nil "function_declarator" "parameters") parent 0) + ;; ((parent-is "template_declaration") parent 0) + + ;; `c-ts-common-looking-at-star' has to come before + ;; `c-ts-common-comment-2nd-line-matcher'. + ;; FIXME: consolidate into a single rule. + ((and (parent-is "comment") c-ts-common-looking-at-star) + c-ts-common-comment-start-after-first-star -1) + (c-ts-common-comment-2nd-line-matcher + c-ts-common-comment-2nd-line-anchor + 1) + ((parent-is "comment") prev-adaptive-prefix 0) + + ;; Labels. + ((node-is "labeled_statement") standalone-parent 0) + ((parent-is "labeled_statement") + c-ts-mode--standalone-grandparent c-ts-mode-indent-offset) + + ;; Preproc directives + ((node-is "preproc_arg") no-indent) + ((node-is "preproc") column-0 0) + ((node-is "#endif") column-0 0) + + ;; C++ + ((node-is "access_specifier") parent-bol 0) + ((prev-line-is "access_specifier") + parent-bol c-ts-mode-indent-offset) + + c-ts-common-baseline-indent-rule))) + (setq rules + (pcase style + ('gnu rules) + ('k&r rules) + ('linux + ;; Reference: + ;; https://www.kernel.org/doc/html/latest/process/coding-style.html, + ;; and script/Lindent in Linux kernel repository. + `(((node-is "labeled_statement") column-0 0) + ,@rules)) + ('bsd + `(((match "compound_statement" "compound_statement") + standalone-parent c-ts-mode-indent-offset) + ((node-is "compound_statement") standalone-parent 0) + ,@rules)))) + (pcase mode + ('c `((c . ,rules))) + ('c++ `((cpp . ,rules)))))) + (defun c-ts-mode--indent-styles (mode) "Indent rules supported by `c-ts-mode'. MODE is either `c' or `cpp'." @@ -558,6 +703,56 @@ MODE is either `c' or `cpp'." ((match "compound_statement" "do_statement") standalone-parent 0) ,@common)))) +(defun c-ts-mode--parenthesized-expression-indent-rule (_node parent &rest _) + "Indent rule that indents aprenthesized expression. + +Aligns the next line to the first sibling + +return (a && b + && c) + +return ( a && b + && c + ) + +Same for if/while statements + +if (a && b + && c) + +NODE, PARENT are the same as other indent rules." + (when (treesit-node-match-p + parent (rx (or "binary" "conditional") "_expression")) + (while (and parent + (not (treesit-node-match-p + parent "parenthesized_expression"))) + (setq parent (treesit-node-parent parent))) + (when parent + (cons (treesit-node-start + (treesit-node-child parent 0 'named)) + 0)))) + +(defun c-ts-mode--label-indent-rules (node parent &rest _) + "Handles indentation around labels. +NODE, PARENT are as usual." + (cond + ;; Matches the top-level labels for GNU-style. + ((and (eq c-ts-mode-indent-style 'gnu) + (treesit-node-match-p node "labeled_statement") + (treesit-node-match-p (treesit-node-parent parent) + "function_definition")) + (cons (pos-bol) 1)) + ;; If previous sibling is a labeled_statement, align to it's + ;; children, which is the previous statement. + ((and (not (treesit-node-match-p node "}")) + (treesit-node-match-p (treesit-node-prev-sibling node) + "labeled_statement")) + (cons (treesit-node-start + (treesit-node-child + (treesit-node-prev-sibling node) 1 'named)) + 0)) + (t nil))) + (defun c-ts-mode--top-level-label-matcher (node parent &rest _) "A matcher that matches a top-level label. NODE should be a labeled_statement. PARENT is its parent." @@ -796,17 +991,17 @@ MODE is either `c' or `cpp'." :feature 'delimiter '((["," ":" ";"]) @font-lock-delimiter-face) - :language mode - :feature 'emacs-devel - :override t - `(((call_expression - (call_expression function: (identifier) @fn) - @c-ts-mode--fontify-DEFUN) - (:match "\\`DEFUN\\'" @fn)) +:language mode +:feature 'emacs-devel +:override t +`(((call_expression + (call_expression function: (identifier) @fn) + @c-ts-mode--fontify-DEFUN) + (:match "\\`DEFUN\\'" @fn)) - ((function_definition type: (_) @for-each-tail) - @c-ts-mode--fontify-for-each-tail - (:match ,c-ts-mode--for-each-tail-regexp @for-each-tail))))) + ((function_definition type: (_) @for-each-tail) + @c-ts-mode--fontify-for-each-tail + (:match ,c-ts-mode--for-each-tail-regexp @for-each-tail))))) ;;; Font-lock helpers @@ -1367,49 +1562,49 @@ in your init files." (treesit-parser-create 'c nil nil 'for-each))) (let ((primary-parser (treesit-parser-create 'c))) - ;; Comments. - (setq-local comment-start "/* ") - (setq-local comment-end " */") - ;; Indent. - (setq-local treesit-simple-indent-rules - (c-ts-mode--get-indent-style 'c)) - ;; Font-lock. - (setq-local treesit-font-lock-settings - (c-ts-mode--font-lock-settings 'c)) - ;; Navigation. - ;; - ;; Nodes like struct/enum/union_specifier can appear in - ;; function_definitions, so we need to find the top-level node. - (setq-local treesit-defun-tactic 'top-level) - (treesit-major-mode-setup) - - ;; Emacs source support: handle DEFUN and FOR_EACH_* gracefully. - (when c-ts-mode-emacs-sources-support - (setq-local add-log-current-defun-function - #'c-ts-mode--emacs-current-defun-name) + ;; Comments. + (setq-local comment-start "/* ") + (setq-local comment-end " */") + ;; Indent. + (setq-local treesit-simple-indent-rules + (c-ts-mode--simple-indent-rules + 'c c-ts-mode-indent-style)) + ;; (setq-local treesit-simple-indent-rules + ;; `((c . ,(alist-get 'gnu (c-ts-mode--indent-styles 'c))))) + ;; Font-lock. + (setq-local treesit-font-lock-settings + (c-ts-mode--font-lock-settings 'c)) + ;; Navigation. + (setq-local treesit-defun-tactic 'top-level) + (treesit-major-mode-setup) - (setq-local treesit-range-settings - (treesit-range-rules 'c-ts-mode--emacs-set-ranges)) + ;; Emacs source support: handle DEFUN and FOR_EACH_* gracefully. + (when c-ts-mode-emacs-sources-support + (setq-local add-log-current-defun-function + #'c-ts-mode--emacs-current-defun-name) - (setq-local treesit-language-at-point-function - (lambda (_pos) 'c)) - (treesit-font-lock-recompute-features '(emacs-devel))) + (setq-local treesit-range-settings + (treesit-range-rules 'c-ts-mode--emacs-set-ranges)) - ;; Inject doxygen parser for comment. - (when (and c-ts-mode-enable-doxygen (treesit-ready-p 'doxygen t)) - (setq-local treesit-primary-parser primary-parser) - (setq-local treesit-font-lock-settings - (append - treesit-font-lock-settings - c-ts-mode-doxygen-comment-font-lock-settings)) - (setq-local treesit-range-settings - (treesit-range-rules - :embed 'doxygen - :host 'c - :local t - `(((comment) @cap - (:match - ,c-ts-mode--doxygen-comment-regex @cap))))))))) + (setq-local treesit-language-at-point-function + (lambda (_pos) 'c)) + (treesit-font-lock-recompute-features '(emacs-devel))) + + ;; Inject doxygen parser for comment. + (when (and c-ts-mode-enable-doxygen (treesit-ready-p 'doxygen t)) + (setq-local treesit-primary-parser primary-parser) + (setq-local treesit-font-lock-settings + (append + treesit-font-lock-settings + c-ts-mode-doxygen-comment-font-lock-settings)) + (setq-local treesit-range-settings + (treesit-range-rules + :embed 'doxygen + :host 'c + :local t + `(((comment) @cap + (:match + ,c-ts-mode--doxygen-comment-regex @cap))))))))) (derived-mode-add-parents 'c-ts-mode '(c-mode)) @@ -1445,7 +1640,8 @@ recommended to enable `electric-pair-mode' with this mode." ;; Indent. (setq-local treesit-simple-indent-rules - (c-ts-mode--get-indent-style 'cpp)) + (c-ts-mode--simple-indent-rules + 'c++ c-ts-mode-indent-style)) ;; Font-lock. (setq-local treesit-font-lock-settings commit e37cd4fa597beaec3b491edb1b15ea0c19e72be4 Author: Yuan Fu Date: Fri Nov 29 20:43:09 2024 -0800 Add baseline tree-sitter indent rule for C-like languages I found a really good baseline indent rule that handles a wide range of situations very well. Now major modes can just start with this rule and add exceptions on top. This is worth mentioning in the manual, but that'll be a large change, and doesn't have to be included in this commit. * lisp/progmodes/c-ts-common.el: (c-ts-common-list-indent-style): New variable. (c-ts-common--standalone-parent): (c-ts-common--prev-standalone-sibling): (c-ts-common-parent-ignore-preproc): (c-ts-common-baseline-indent-rule): New function. * lisp/treesit.el (treesit--indent-prev-line-node): New function. (treesit-simple-indent-presets): Add new preset. diff --git a/lisp/progmodes/c-ts-common.el b/lisp/progmodes/c-ts-common.el index d05b5248cc2..ab5c01bad2d 100644 --- a/lisp/progmodes/c-ts-common.el +++ b/lisp/progmodes/c-ts-common.el @@ -532,6 +532,169 @@ characters on the current line." (setq node (treesit-node-parent node))) (* level (symbol-value c-ts-common-indent-offset)))) +;;; Baseline indent rule + +(defvar c-ts-common-list-indent-style 'align + "Intructs `c-ts-common-baseline-indent-rule' how to indent lists. + +If the value is `align', indent lists like this: + +const a = [ + 1, 2, 3 + 4, 5, 6, + ]; + +If the value is `simple', indent lists like this: + +const a = [ + 1, 2, 3, + 4, 5, 6, +];") + +(defun c-ts-common--standalone-parent (parent) + "Find the first parent that starts on a new line. +Start searching from PARENT, so if PARENT satisfies the condition, it'll +be returned. Return the starting position of the parent, return nil if +no parent satisfies the condition." + (save-excursion + (catch 'term + (while parent + (goto-char (treesit-node-start parent)) + (when (looking-back (rx bol (* whitespace)) + (line-beginning-position)) + (throw 'term (point))) + (setq parent (treesit-node-parent parent)))))) + +(defun c-ts-common--prev-standalone-sibling (node) + "Return the previous sibling of NODE that starts on a new line. +Return nil if no sibling satisfies the condition." + (save-excursion + (setq node (treesit-node-prev-sibling node 'named)) + (goto-char (treesit-node-start node)) + (while (and node + (goto-char (treesit-node-start node)) + (not (looking-back (rx bol (* whitespace)) + (pos-bol)))) + (setq node (treesit-node-prev-sibling node 'named))) + node)) + +(defun c-ts-common-parent-ignore-preproc (node) + "Return the parent of NODE, skipping preproc nodes." + (let ((parent (treesit-node-parent node)) + (pred (if (treesit-thing-defined-p + 'preproc (or (and node (treesit-node-language node)) + (treesit-parser-language + treesit-primary-parser))) + 'preproc + "preproc"))) + (while (and parent (treesit-node-match-p parent pred)) + (setq parent (treesit-node-parent parent))) + parent)) + +(defun c-ts-common-baseline-indent-rule (node parent bol &rest _) + "Baseline indent rule for C-like languages. + +NODE PARENT, BOL are like in other simple indent rules. + +This rule works as follows: + +Let PREV-NODE be the largest node that starts on previous line, +basically the NODE we get if we were indenting previous line. + +0. Closing brace aligns with first parent that starts on a new line. + +1. If PREV-NODE and NODE are siblings, align this line to previous +line (PREV-NODE as the anchor, and offset is 0). + +2. If PARENT is a list, ie, (...) [...], align with NODE's first +sibling. For the first sibling and the closing paren or bracket, indent +according to `c-ts-common-list-indent-style'. This rule also handles +initializer lists like {...}, but initializer lists doesn't respect +`c-ts-common-list-indent-style'--they always indent in the `simple' +style. + +3. Otherwise, go up the parse tree from NODE and look for a parent that +starts on a new line. Use that parent as the anchor and indent one +level. But if the node is a top-level construct (ignoring preprocessor +nodes), don't indent it. + +This rule works for a wide range of scenarios including complex +situations. Major modes should use this as the fallback rule, and add +exception rules before it to cover the cases it doesn't apply. + +This rule tries to be smart and ignore proprocessor node in some +situations. By default, any node that has \"proproc\" in its type are +considered a preprocessor node. If that heuristic is inaccurate, define +a `preproc' thing in `treesit-thing-settings', and this rule will use +the thing definition instead." + (let ((prev-line-node (treesit--indent-prev-line-node bol)) + (offset (symbol-value c-ts-common-indent-offset))) + (cond + ;; Condition 0. + ((and (treesit-node-match-p node "}") + (treesit-node-match-p (treesit-node-child parent 0) "{")) + (cons (c-ts-common--standalone-parent parent) + 0)) + ;; Condition 1. + ((and (treesit-node-eq (treesit-node-parent prev-line-node) + parent) + (not (treesit-node-match-p node (rx (or ")" "]"))))) + (cons (treesit-node-start prev-line-node) + 0)) + ;; Condition 2. + ((treesit-node-match-p (treesit-node-child parent 0) + (rx (or "(" "["))) + (let ((first-sibling (treesit-node-child parent 0 'named))) + (cond + ;; Closing delimeters. + ((treesit-node-match-p node (rx (or ")" "]"))) + (if (eq c-ts-common-list-indent-style 'align) + (cons (treesit-node-start (treesit-node-child parent 0)) + 0) + (cons (c-ts-common--standalone-parent parent) + 0))) + ;; First sibling. + ((treesit-node-eq node first-sibling) + (if (eq c-ts-common-list-indent-style 'align) + (cons (treesit-node-start (treesit-node-child parent 0)) + 1) + (cons (c-ts-common--standalone-parent parent) + offset))) + ;; Not first sibling + (t (cons (treesit-node-start + (or (c-ts-common--prev-standalone-sibling node) + first-sibling)) + 0))))) + ;; Condition 2 for initializer list, only apply to + ;; second line. Eg, + ;; + ;; return { 1, 2, 3, + ;; 4, 5, 6, --> Handled by this condition. + ;; 7, 8, 9 }; --> Handled by condition 1. + ((and (treesit-node-match-p (treesit-node-child parent 0) "{") + (treesit-node-prev-sibling node 'named)) + ;; If first sibling is a comment, indent like code; otherwise + ;; align to first sibling. + (if (treesit-node-match-p + (treesit-node-child parent 0 'named) + c-ts-common--comment-regexp 'ignore-missing) + (cons (c-ts-common--standalone-parent parent) + offset) + (cons (treesit-node-start + (treesit-node-child parent 0 'named)) + 0))) + ;; Before we fallback to condition 3, make sure we don't indent + ;; top-level stuff. + ((treesit-node-eq (treesit-parser-root-node + (treesit-node-parser parent)) + (c-ts-common-parent-ignore-preproc node)) + (cons (pos-bol) 0)) + ;; Condition 3. + (t (cons (c-ts-common--standalone-parent parent) + offset))))) + + + (provide 'c-ts-common) ;;; c-ts-common.el ends here diff --git a/lisp/treesit.el b/lisp/treesit.el index 3539942f19a..2acb46ab105 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1589,6 +1589,14 @@ should take the same argument as MATCHER or ANCHOR. If it matches, return a cons (ANCHOR-POS . OFFSET), where ANCHOR-POS is a position and OFFSET is the indent offset; if it doesn't match, return nil.") +(defun treesit--indent-prev-line-node (pos) + "Return the largest node on the previous line of POS." + (save-excursion + (goto-char pos) + (when (eq (forward-line -1) 0) + (back-to-indentation) + (treesit--indent-largest-node-at (point))))) + (defvar treesit-simple-indent-presets (list (cons 'match (lambda @@ -1639,6 +1647,12 @@ OFFSET is the indent offset; if it doesn't match, return nil.") (lambda (node &rest _) (string-match-p type (or (treesit-node-type node) ""))))) + ;; FIXME: Add to manual. + (cons 'prev-line-is (lambda (type) + (lambda (_n _p bol &rest _) + (treesit-node-match-p + (treesit--indent-prev-line-node bol) + type)))) (cons 'field-is (lambda (name) (lambda (node &rest _) (string-match-p commit 4afdb7e80febd56f4024bad0aff4356198f6ce53 Author: Yuan Fu Date: Fri Nov 29 20:39:00 2024 -0800 ; Minor simplification for tree-sitter indent preset column-0 * lisp/treesit.el (treesit-simple-indent-presets): Simplify. diff --git a/lisp/treesit.el b/lisp/treesit.el index 2dd556e55ab..3539942f19a 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1744,10 +1744,7 @@ OFFSET is the indent offset; if it doesn't match, return nil.") (forward-line -1) (skip-chars-forward " \t") (point)))) - (cons 'column-0 (lambda (_n _p bol &rest _) - (save-excursion - (goto-char bol) - (line-beginning-position)))) + (cons 'column-0 (lambda (&rest _) (pos-bol))) ;; TODO: Document. (cons 'and (lambda (&rest fns) (lambda (node parent bol &rest _) commit d0b918d8f3cc0779fa73b395b6384c8a04b0c0ba Author: Yuan Fu Date: Fri Nov 29 20:38:24 2024 -0800 Add treesit-explore command * lisp/treesit.el (treesit-explore): New function. * doc/lispref/parsing.texi (Language Grammar): Advertize this command instead of the minor mode. diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index 9fe828d8512..5ef29f558ef 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -240,8 +240,8 @@ which displays the syntax tree of the source in the current buffer in real time. Emacs also comes with an ``inspect mode'', which displays information of the nodes at point in the mode-line. -@deffn Command treesit-explore-mode -This mode pops up a window displaying the syntax tree of the source in +@deffn Command treesit-explore +This command pops up a window displaying the syntax tree of the source in the current buffer. Selecting text in the source buffer highlights the corresponding nodes in the syntax tree display. Clicking on nodes in the syntax tree highlights the corresponding text in the diff --git a/lisp/treesit.el b/lisp/treesit.el index 64b08857f4a..2dd556e55ab 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3632,6 +3632,14 @@ window." #'treesit--explorer-kill-explorer-buffer t) (treesit--explorer-kill-explorer-buffer))) +(defun treesit-explore () + "Show the explorer." + (interactive) + (if (and treesit-explore-mode + (buffer-live-p treesit--explorer-buffer)) + (display-buffer treesit--explorer-buffer '(nil (inhibit-same-window . t))) + (treesit-explore-mode))) + ;;; Install & build language grammar (defvar treesit-language-source-alist nil commit 1e44c63fcadeb89a9a9c9208f221970e6fcc774c Author: Yuan Fu Date: Fri Nov 29 14:10:45 2024 -0800 Allow treesit-simple-indent's rule to be a single function * lisp/treesit.el (treesit-simple-indent-rules): Allow the rule to be a single function. Also replace cl-loop with dolist plus catch-throw. (treesit--indent-rules-optimize): Handle the case when a rule is a function. * doc/lispref/modes.texi (Parser-based Indentation): Update manuel. diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index 87c6347eaa7..f32d5a89bca 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -5243,11 +5243,13 @@ more complex indentation engines. @cindex indentation rules, for parser-based indentation @defvar treesit-simple-indent-rules -This local variable stores indentation rules for every language. It -is an alist with elements of the form @w{@code{(@var{language} -. @var{rules})}}, where @var{language} is a language symbol, and -@var{rules} is a list with elements of the form -@w{@code{(@var{matcher} @var{anchor} @var{offset})}}. +This local variable stores indentation rules for every language. It is +an list of elements of the form @w{@code{(@var{language} +@var{rule}...)}}, where @var{language} is a language symbol, and each +@var{rule} is either a list with elements of the form +@w{@code{(@var{matcher} @var{anchor} @var{offset})}}, or a function. Let +'s focus on the list variant first, we'll come back to the function +variant later. First, Emacs passes the smallest tree-sitter node at the beginning of the current line to @var{matcher}; if it returns non-@code{nil}, this @@ -5277,6 +5279,15 @@ and @var{anchor} should return a buffer position. or a function that returns an integer. If it is a function, it is passed @var{node}, @var{parent}, and @var{bol}, like matchers and anchors. + +Remember that @var{rule} can also be a function. This is for the +complex cases where a rule needs to consider the matching rule and +anchor together. If @var{rule} is a function, it's passed the same +argument as @var{matcher}: @var{node}, @var{parent}, and @var{bol}. If +it matches, @var{rule} should return a cons @w{@code{(@var{anchor-pos} +. @var{offset})}}, where @var{anchor-pos} is a buffer position, and +@var{offset} is the indent offset. If @var{rule} does't match, it +should return @code{nil}. @end defvar @defvar treesit-simple-indent-presets diff --git a/lisp/treesit.el b/lisp/treesit.el index 5d57b9e8e3e..64b08857f4a 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1559,13 +1559,13 @@ START and END mark the current to-be-propertized region." (defvar-local treesit-simple-indent-rules nil "A list of indent rule settings. -Each indent rule setting should be (LANGUAGE . RULES), -where LANGUAGE is a language symbol, and RULES is a list of +Each indent rule setting should be (LANGUAGE RULE...), where LANGUAGE is +a language symbol, and each RULE is of the form - (MATCHER ANCHOR OFFSET). + (MATCHER ANCHOR OFFSET) -MATCHER determines whether this rule applies, ANCHOR and OFFSET -together determines which column to indent to. +MATCHER determines whether this rule applies, ANCHOR and +OFFSET together determines which column to indent to. A MATCHER is a function that takes three arguments (NODE PARENT BOL). BOL is the point where we are indenting: the beginning of @@ -1582,7 +1582,12 @@ ANCHOR and adds OFFSET to it, and indents to that column. OFFSET can be an integer or a variable whose value is an integer. For MATCHER and ANCHOR, Emacs provides some convenient presets. -See `treesit-simple-indent-presets'.") +See `treesit-simple-indent-presets'. + +For complex cases, a RULE can also be a single function. This function +should take the same argument as MATCHER or ANCHOR. If it matches, +return a cons (ANCHOR-POS . OFFSET), where ANCHOR-POS is a position and +OFFSET is the indent offset; if it doesn't match, return nil.") (defvar treesit-simple-indent-presets (list (cons 'match @@ -2121,31 +2126,37 @@ OFFSET." (let* ((language (treesit-node-language parent)) (rules (alist-get language treesit-simple-indent-rules))) - (cl-loop for rule in rules - for pred = (nth 0 rule) - for anchor = (nth 1 rule) - for offset = (nth 2 rule) - if (treesit--simple-indent-eval - (list pred node parent bol)) - do (when treesit--indent-verbose + (catch 'match + (dolist (rule rules) + (if (functionp rule) + (let ((result (funcall rule node parent bol))) + (when result + (when treesit--indent-verbose (message "Matched rule: %S" rule)) - and - return - (let ((anchor-pos - (treesit--simple-indent-eval - (list anchor node parent bol))) - (offset-val - (cond ((numberp offset) offset) - ((and (symbolp offset) - (boundp offset)) - (symbol-value offset)) - (t (treesit--simple-indent-eval - (list offset node parent bol)))))) - (cons anchor-pos offset-val)) - finally return - (progn (when treesit--indent-verbose - (message "No matched rule")) - (cons nil nil)))))) + (throw 'match result))) + (let ((pred (nth 0 rule)) + (anchor (nth 1 rule)) + (offset (nth 2 rule))) + ;; Found a match. + (when (treesit--simple-indent-eval + (list pred node parent bol)) + (when treesit--indent-verbose + (message "Matched rule: %S" rule)) + (let ((anchor-pos + (treesit--simple-indent-eval + (list anchor node parent bol))) + (offset-val + (cond ((numberp offset) offset) + ((and (symbolp offset) + (boundp offset)) + (symbol-value offset)) + (t (treesit--simple-indent-eval + (list offset node parent bol)))))) + (throw 'match (cons anchor-pos offset-val))))))) + ;; Didn't find any match. + (when treesit--indent-verbose + (message "No matched rule")) + (cons nil nil))))) (defun treesit--read-major-mode () "Read a major mode using completion. @@ -2201,12 +2212,14 @@ RULES." (_ func))) ;; Optimize a rule (MATCHER ANCHOR OFFSET). (optimize-rule (rule) - (let ((matcher (nth 0 rule)) - (anchor (nth 1 rule)) - (offset (nth 2 rule))) - (list (optimize-func matcher) - (optimize-func anchor) - offset)))) + (if (functionp rule) + rule + (let ((matcher (nth 0 rule)) + (anchor (nth 1 rule)) + (offset (nth 2 rule))) + (list (optimize-func matcher) + (optimize-func anchor) + offset))))) (cons lang (mapcar #'optimize-rule indent-rules))))) ;;; Search commit 1d425125694b61e2b07a7b429a6475dfe59afe37 Author: Yuan Fu Date: Fri Nov 29 13:45:24 2024 -0800 Refactor treesit--indent-1 * lisp/treesit.el (treesit--indent-largest-node-at): New function. (treesit--indent-1): Extract code out into the new function. diff --git a/lisp/treesit.el b/lisp/treesit.el index 6f5d065cc24..5d57b9e8e3e 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1943,41 +1943,44 @@ PARENT is its parent; ANCHOR is a point (not a node), and OFFSET is a number. Emacs finds the column of ANCHOR and adds OFFSET to it as the final indentation of the current line.") -(defun treesit--indent-1 () - "Indent the current line. -Return (ANCHOR . OFFSET). This function is used by -`treesit-indent' and `treesit-indent-region'." - ;; Basically holds the common part between the two indent function. - (let* ((bol (save-excursion - (forward-line 0) - (skip-chars-forward " \t") - (point))) - (local-parsers (treesit-local-parsers-at bol nil t)) +(defun treesit--indent-largest-node-at (pos) + "Get largest node that still starts at POS." + (let* ((local-parsers (treesit-local-parsers-at pos nil t)) (smallest-node (cond ((car local-parsers) (let ((local-parser (caar local-parsers)) (host-parser (cdar local-parsers))) (if (eq (treesit-node-start (treesit-parser-root-node local-parser)) - bol) - (treesit-node-at bol host-parser) - (treesit-node-at bol local-parser)))) + pos) + (treesit-node-at pos host-parser) + (treesit-node-at pos local-parser)))) ((null (treesit-parser-list)) nil) ((eq 1 (length (treesit-parser-list nil nil t))) - (treesit-node-at bol)) - ((treesit-language-at bol) - (treesit-node-at bol (treesit-language-at bol))) - (t (treesit-node-at bol)))) + (treesit-node-at pos)) + ((treesit-language-at pos) + (treesit-node-at pos (treesit-language-at pos))) + (t (treesit-node-at pos)))) (root (treesit-parser-root-node - (treesit-node-parser smallest-node))) - (node (treesit-parent-while - smallest-node - (lambda (node) - (and (eq bol (treesit-node-start node)) - (not (treesit-node-eq node root))))))) - (let* - ((parser (if smallest-node - (treesit-node-parser smallest-node) + (treesit-node-parser smallest-node)))) + (treesit-parent-while + smallest-node + (lambda (node) + (and (eq pos (treesit-node-start node)) + (not (treesit-node-eq node root))))))) + +(defun treesit--indent-1 () + "Indent the current line. +Return (ANCHOR . OFFSET). This function is used by +`treesit-indent' and `treesit-indent-region'." + ;; Basically holds the common part between the two indent function. + (let* ((bol (save-excursion + (forward-line 0) + (skip-chars-forward " \t") + (point))) + (node (treesit--indent-largest-node-at bol)) + (parser (if node + (treesit-node-parser node) nil)) ;; NODE would be nil if BOL is on a whitespace. In that case ;; we set PARENT to the "node at point", which would @@ -1985,7 +1988,7 @@ Return (ANCHOR . OFFSET). This function is used by (parent (cond ((and node parser) (treesit-node-parent node)) (t (treesit-node-on bol bol))))) - (funcall treesit-indent-function node parent bol)))) + (funcall treesit-indent-function node parent bol))) (defun treesit-indent () "Indent according to the result of `treesit-indent-function'." commit c65c5d02224335562be7e04dd1bf185dfca3dbf1 Author: Jørgen Kvalsvik Date: Tue Nov 19 08:01:01 2024 +0100 Improve c-ts-mode compound indents (bug#74507) Properly indent the body of compound expressions, even when then compound expression is not at the beginning of line and the parent is not an if/for/while/etc., and matches the behavior of c-mode. This fixes a problem that is common with macros and in testing frameworks. For example, you expect this to indent: TEST_CASE(1) { assert (...); } If the compound statement is the function body itself, don't apply this new rule and instead guide by the parent and first sibling. I'm sure there are subtle interactions that aren't handled properly by checking for "function_definition" rather than something more general, but it does fix the test case and the check can be improved as more cases are found. * lisp/progmodes/c-ts-mode.el: (c-ts-mode--parent-is-not-top-compound): New function. (c-ts-mode--indent-styles): Use it. * test/lisp/progmodes/c-ts-mode-resources/indent.erts: New compound statement test. diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index cbb103cfaf7..59b34ef6b8b 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -403,6 +403,12 @@ PARENT is its parent." (treesit-node-start parent) (line-end-position)))))) +(defun c-ts-mode--parent-is-not-top-compound (_n parent &rest _) + "Matches when PARENT is not the top level compound statement. +The top-level compound is the {} that immediately follows the function +signature." + (not (equal "function_definition" (treesit-node-type (treesit-node-parent parent))))) + (defun c-ts-mode--indent-styles (mode) "Indent rules supported by `c-ts-mode'. MODE is either `c' or `cpp'." @@ -479,6 +485,7 @@ MODE is either `c' or `cpp'." ;; Closing bracket. This should be before initializer_list ;; (and probably others) rule because that rule (and other ;; similar rules) will match the closing bracket. (Bug#61398) + ((and (node-is "}") c-ts-mode--parent-is-not-top-compound) parent-bol 0) ((node-is "}") standalone-parent 0) ,@(when (eq mode 'cpp) '(((node-is "access_specifier") parent-bol 0) @@ -498,6 +505,8 @@ MODE is either `c' or `cpp'." ((parent-is "field_declaration_list") c-ts-mode--anchor-prev-sibling 0) ;; Statement in {} blocks. + ((and (parent-is "compound_statement") c-ts-mode--parent-is-not-top-compound) + parent-bol c-ts-mode-indent-offset) ((or (and (parent-is "compound_statement") ;; If the previous sibling(s) are not on their ;; own line, indent as if this node is the first diff --git a/test/lisp/progmodes/c-ts-mode-resources/indent.erts b/test/lisp/progmodes/c-ts-mode-resources/indent.erts index 2f3540c3970..61e61677ed7 100644 --- a/test/lisp/progmodes/c-ts-mode-resources/indent.erts +++ b/test/lisp/progmodes/c-ts-mode-resources/indent.erts @@ -223,6 +223,36 @@ make_pair(int long_identifier_a[], int long_identifier_b[], =-=-= +Name: Compound Statement after code + +=-= +#define IOTA(var, n) for (int var = 0; var != (n); ++var) +int main() +{ +IOTA (v, 10) { +printf("%d ", v); +} + +const char *msg = "Hello, world!"; { +puts("Hello, world!"); +} +} + +=-= +#define IOTA(var, n) for (int var = 0; var != (n); ++var) +int main() +{ + IOTA (v, 10) { + printf("%d ", v); + } + + const char *msg = "Hello, world!"; { + puts("Hello, world!"); + } +} + +=-=-= + Name: Switch-Case statement =-= commit 4afd1eca3662eda052d0017f83b4baa1f6131e8b Merge: 676ff9fd7c0 cf4f1387a65 Author: Yuan Fu Date: Sun Dec 1 17:52:16 2024 -0800 Merge from emacs-30 cf4f1387a65 ; Update tree-sitter manual 3c7687c1dd1 Allow passing nil to treesit-node-match-p (bug#74612) 748b19e56e8 Update to version 2.58 of librsvg API (bug#74606) 4c67f636c08 Fix decoding of non-ASCII email attachments bd8a6f70fb9 Prevent "Selecting deleted buffer" error with dabbrev-expand 0a753603a53 ; (dictionary-search-interface): Fix bug#74511. commit 676ff9fd7c083598b2955a463774283768d8e209 Author: Stefan Monnier Date: Sun Dec 1 18:58:04 2024 -0500 (asm-mode): Support file-local `asm-comment-char` (bug#74447) * lisp/progmodes/asm-mode.el (asm-mode): Move comment settings to `:after-hook`. diff --git a/lisp/progmodes/asm-mode.el b/lisp/progmodes/asm-mode.el index d47c525c5f9..6fff11bb39f 100644 --- a/lisp/progmodes/asm-mode.el +++ b/lisp/progmodes/asm-mode.el @@ -125,21 +125,24 @@ Turning on Asm mode runs the hook `asm-mode-hook' at the end of initialization. Special commands: \\{asm-mode-map}" + :after-hook + (progn + (run-hooks 'asm-mode-set-comment-hook) + ;; Make our own local child of `asm-mode-map' + ;; so we can define our own comment character. + (use-local-map (make-composed-keymap nil asm-mode-map)) + (local-set-key (vector asm-comment-char) #'asm-comment) + (set-syntax-table (make-syntax-table asm-mode-syntax-table)) + (modify-syntax-entry asm-comment-char "< b") + + (setq-local comment-start (string asm-comment-char))) + (setq local-abbrev-table asm-mode-abbrev-table) (setq-local font-lock-defaults '(asm-font-lock-keywords)) (setq-local indent-line-function #'asm-indent-line) ;; Stay closer to the old TAB behavior (was tab-to-tab-stop). (setq-local tab-always-indent nil) - (run-hooks 'asm-mode-set-comment-hook) - ;; Make our own local child of `asm-mode-map' - ;; so we can define our own comment character. - (use-local-map (nconc (make-sparse-keymap) asm-mode-map)) - (local-set-key (vector asm-comment-char) #'asm-comment) - (set-syntax-table (make-syntax-table asm-mode-syntax-table)) - (modify-syntax-entry asm-comment-char "< b") - - (setq-local comment-start (string asm-comment-char)) (setq-local comment-add 1) (setq-local comment-start-skip "\\(?:\\s<+\\|/[/*]+\\)[ \t]*") (setq-local comment-end-skip "[ \t]*\\(\\s>\\|\\*+/\\)") commit 6c1c3120b98652de149ee9d8c241cd3636755171 Author: Alan Mackenzie Date: Sun Dec 1 19:46:16 2024 +0000 CC Mode manual: Better document lines starting with a label * doc/misc/cc-mode.texi (Syntactic Analysis): Document analysis of lines starting with labels, and their non-use as anchor positions. diff --git a/doc/misc/cc-mode.texi b/doc/misc/cc-mode.texi index ccdd6a97787..5f46c1577da 100644 --- a/doc/misc/cc-mode.texi +++ b/doc/misc/cc-mode.texi @@ -4177,6 +4177,52 @@ and you can see that the syntactic context contains two syntactic elements. Notice that the first element, @samp{(comment-intro)}, has no anchor position. +@cindex label line +There are special ways of handling lines beginning with labels. Such +a line gets a syntactic element beginning with @code{label} or +@code{substatement-label} rather than the element(s) it would have +had, were there no label on the line. + +Also, a line beginning with a label (or a comment) is never the anchor +position of a later line. Instead, that anchor position is the latest +line at the same level of nesting before the labeled line without a +leading label or comment. If there is no such line, the latest line +containing an enclosing opening brace or parenthesis, which doesn't +start with a label or comment, provides the anchor postion. In this +case extra syntactic element(s) with syntactic symbol +@code{defun-block-intro}, @code{statement-block-intro}, or some other +``-intro'' symbol are inserted into the syntactic context to allow the +correct indentation of the later line using that anchor position. + +These conventions allow a style to indent labels specially, perhaps +giving them greater visibility by indenting them less than the +surrounding code. + +For example, in the following pike fragment: + +@example + 1: int a() + 2: @{ + 3: foo: @{ + 4: bar: if (t) + 5: x; + 6: y; + 7: @} + 8: y; + 9: @} +@end example + +@noindent +Line 4 gets the syntactic context + +@example +((defun-block-intro 9) (label 9)) +@end example + +@noindent +where position 9 is the brace on line 2, the latest line before line 4 +without a label. + @comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @node Syntactic Symbols commit cf4f1387a6561be7cd7387b766df4386a0fa472f (refs/remotes/origin/emacs-30) Author: Yuan Fu Date: Sun Dec 1 11:22:48 2024 -0800 ; Update tree-sitter manual * doc/lispref/parsing.texi (User-defined Things): Add manual entry for treesit-node-match-p, treesit-thing-defined-p, treesit-thing-definition. Change wording for some functions. diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index 8f3c2b4a366..f8bf0b20a7c 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -1536,7 +1536,11 @@ The ``things'' feature in Emacs is independent of the pattern matching feature of tree-sitter, and comparatively less powerful, but more suitable for navigation and traversing the parse tree. -You can define things with @code{treesit-thing-settings}. +@findex treesit-thing-definition +@findex treesit-thing-defined-p +You can define things with @var{treesit-thing-settings}, retrieve the +predicate of a defined thing with @code{treesit-thing-definition}, and +test if a thing is defined with @code{treesit-thing-defined-p}. @defvar treesit-thing-settings This is an alist of thing definitions for each language. The key of @@ -1592,6 +1596,25 @@ functions listed elsewhere also utilize the thing feature, e.g., tree-traversing functions like @code{treesit-search-forward}, @code{treesit-induce-sparse-tree}, etc. @xref{Retrieving Nodes}. +@defun treesit-node-match-p node thing &optional ignore-missing +This function checks whether @var{node} is a @var{thing}. + +If @var{node} is a @var{thing}, return non-@code{nil}, otherwise return +@code{nil}. For convenience, if @code{node} is @code{nil}, this +function just returns @code{nil}. + +The @var{thing} can be either a thing symbol like @code{defun}, or +simply a predicate that defines a thing, like +@code{"function_definition"}, or @w{@code{(or comment string)}}. + +By default, if @var{thing} is undefined or malformed, this function +signals @code{treesit-invalid-predicate} error. If @var{ignore-missing} +is @code{t}, this function doesn't signal the error when @var{thing} is +undefined and just returns @code{nil}; but it still signals the error if +@var{thing} is a malformed predicate. + +@end defun + @defun treesit-thing-prev position thing This function returns the first node before @var{position} that is the specified @var{thing}. If no such node exists, it returns @code{nil}. @@ -1599,8 +1622,7 @@ It's guaranteed that, if a node is returned, the node's end position is less or equal to @var{position}. In other words, this function never returns a node that encloses @var{position}. -@var{thing} can be either a thing symbol like @code{defun}, or simply a -thing definition like @code{"function_definition"}. +Again, @var{thing} can be either a symbol or a predicate. @end defun @defun treesit-thing-next position thing @@ -1624,7 +1646,7 @@ A positive @var{arg} means moving forward that many instances of @code{end}, stop at the end of @var{thing}. Like in @code{treesit-thing-prev}, @var{thing} can be a thing symbol -defined in @code{treesit-thing-settings}, or a thing definition. +defined in @code{treesit-thing-settings}, or a predicate. @var{tactic} determines how this function moves between things. It can be @code{nested}, @code{top-level}, @code{restricted}, or @code{nil}. @@ -1651,7 +1673,7 @@ i.e., start position must be strictly greater than @var{position}, and end position must be strictly less than @var{position}. @var{thing} can be either a thing symbol defined in -@code{treesit-thing-settings}, or a thing definition. +@code{treesit-thing-settings}, or a predicate. @end defun @findex treesit-beginning-of-thing commit 3c7687c1dd136fa535e22262f78fdfadbbf73105 Author: Yuan Fu Date: Fri Nov 29 16:33:28 2024 -0800 Allow passing nil to treesit-node-match-p (bug#74612) * src/treesit.c (Ftreesit_node_match_p): Return nil if NODE is nil. diff --git a/src/treesit.c b/src/treesit.c index 4031d80f7c9..cda6d4af2ee 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -4017,7 +4017,8 @@ PREDICATE can be a symbol representing a thing in `treesit-thing-settings', or a predicate, like regexp matching node type, etc. See `treesit-thing-settings' for more details. -Return non-nil if NODE matches PREDICATE, nil otherwise. +Return non-nil if NODE matches PREDICATE, nil otherwise. If NODE is +nil, return nil. Signals `treesit-invalid-predicate' if there's no definition of THING in `treesit-thing-settings', or if PREDICATE is malformed. If @@ -4025,6 +4026,8 @@ IGNORE-MISSING is non-nil, don't signal an error for missing THING definition, but still signal for malformed PREDICATE. */) (Lisp_Object node, Lisp_Object predicate, Lisp_Object ignore_missing) { + if (NILP (node)) return Qnil; + CHECK_TS_NODE (node); Lisp_Object parser = XTS_NODE (node)->parser; commit 3bf4ea9543fedb7d3af42d37c853a6f29c681523 Author: F. Jason Park Date: Mon Nov 25 00:01:04 2024 -0800 Reserve erc-normalize-port for equality comparisons * etc/ERC-NEWS: Add entry explaining changes to entry point 'erc-tls' and library functions `erc-normalize-port' and `erc-compute-port'. * lisp/erc/erc.el (erc-normalize-port): Map "ircu" to 6665 instead of 6667, and add related IANA service mappings. Return 0 for unknown nonempty strings. (erc-open): Pass `erc-session-port' through `erc-string-to-port' before handing it to `erc-server-connect'. This prevents the network machinery from ever seeing a numeric string, like "6667". (erc-select-read-args): Since `erc-compute-port' no longer relies on `erc-normalize-port', ensure its input is a number. Use `erc-port-equal' instead of `eql'. (erc-tls): Respect a configured non-nil `erc-port' option when the user does not provide a :port keyword argument from Lisp code. (erc-determine-parameters): Use `erc-compute-port' for initializing `erc-session-port'. (erc-compute-port): Don't pass the result through `erc-normalize-port', which can convert it to an unintuitive form. (erc--url-default-connect-function): Use `erc-compute-port' instead of `erc-normalize-port'. (erc-handle-irc-url): Use `erc-port-equal' for comparison. * test/lisp/erc/erc-scenarios-auth-source.el (erc-scenarios-common--auth-source): Allow tests to convey the automatic port number to `erc-open' via `erc-port'. (erc-scenarios-base-auth-source-server--dialed): Use `erc-port' option instead of passing a :port parameter to entry-point command. * test/lisp/erc/erc-tests.el (erc-normalize-port): New test. (Bug#74516) diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 3970f67d725..f3c8645f02d 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -62,6 +62,32 @@ of concerns and the newer module's "experimental" status, the migration was deemed worth any potential disruption, despite this being a point release. ERC appreciates your understanding in this matter. +** Entry-point command 'erc-tls' once again considers option 'erc-port'. +In its zeal to enforce a preference for TLS connections, ERC 5.5 went a +bit far in disregarding the useful user option 'erc-port'. When called +from Lisp code without a ':port' keyword, 'erc-tls' once again respects +the option. + +** Changes in the library API. + +*** Function 'erc-normalize-port' may return 0 instead of nil. +When given a nonempty, non-numeric string, this function now returns 0. +Moreover, ERC officially requests that users not use its output for +anything but comparing port equality, which was always its intended +purpose. + +*** Function 'erc-compute-port' no longer uses 'erc-normalize-port'. +An uninformed change in ERC 5.5 led to 'erc-compute-port' filtering its +result through 'erc-normalize-port', which brought unwelcome type +coercion and possible null return values. This defied its purpose of +ensuring a usable port. Users reliant on the aberrant 5.5 behavior +should wrap its return value in 'erc-normalize-port'. + +*** Local variable 'erc-session-port' may be a string. +Although this has always been the case, string values are now more +likely to be seen because ERC no longer coerces service names to port +numbers. + * Changes in ERC 5.6 diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index ab31696b669..ad279a0ff66 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -1987,13 +1987,12 @@ the existing buffers will be reused." "old behavior when t now permanent" "29.1") (defun erc-normalize-port (port) - "Normalize the port specification PORT to integer form. -PORT may be an integer, a string or a symbol. If it is a string or a -symbol, it may have these values: -* irc -> 194 -* ircs -> 994 -* ircd -> 6667 -* ircd-dalnet -> 7000" + "Normalize known PORT specifications to an integer. +Expect PORT to be an integer, a string, or a symbol to coerce into a +standardized form for the express purpose of equality comparisons. If +PORT is an IANA recognized service, return its numeric mapping. Do the +same for a few traditional but nonstandard names. Return nil in +pathological cases." ;; These were updated somewhat in 2022 to reflect modern standards ;; and practices. See also: ;; @@ -2001,7 +2000,7 @@ symbol, it may have these values: ;; https://www.iana.org/assignments/service-names-port-numbers (cond ((symbolp port) - (erc-normalize-port (symbol-name port))) + (and port (erc-normalize-port (symbol-name port)))) ((stringp port) (let ((port-nr (string-to-number port))) (cond @@ -2011,14 +2010,19 @@ symbol, it may have these values: 194) ((string-equal port "ircs") 994) - ((string-equal port "ircu") 6667) ; 6665-6669 + ((string-equal port "ircu") 6665) + ((string-equal port "ircu-2") 6666) + ((string-equal port "ircu-3") 6667) + ((string-equal port "ircu-4") 6668) + ((string-equal port "ircu-5") 6669) ((string-equal port "ircd") ; nonstandard (irc-serv is 529) 6667) ((string-equal port "ircs-u") 6697) ((string-equal port "ircd-dalnet") 7000) + ((string-empty-p port) nil) (t - nil)))) + 0)))) ((numberp port) port) (t @@ -2665,7 +2669,7 @@ side effect of setting the current buffer to the one it returns. Use (if connect (erc-server-connect erc-session-server - erc-session-port + (erc-string-to-port erc-session-port) buffer erc-session-client-certificate) (erc-update-mode-line)) @@ -2769,8 +2773,8 @@ properties needed by entry-point commands, like `erc-tls'." (port (or (url-portspec url) (erc-compute-port (let ((d (erc-compute-port sp))) ; may be a string - (read-string (format-prompt "Port" d) - nil nil d))))) + (erc-string-to-port + (read-string (format-prompt "Port" d) nil nil d)))))) ;; Trust the user not to connect twice accidentally. We ;; can't use `erc-already-logged-in' to check for an existing ;; connection without modifying it to consider USER and PASS. @@ -2792,10 +2796,10 @@ properties needed by entry-point commands, like `erc-tls'." (format-prompt "Server password" p) "Server password (optional): "))) (if erc-prompt-for-password (read-passwd m nil p) p))) - (opener (and (or sp (eql port erc-default-port-tls) + (opener (and (or sp (erc-port-equal port erc-default-port-tls) (and (equal server erc-default-server) (not (string-prefix-p "irc://" input)) - (eql port erc-default-port) + (erc-port-equal port erc-default-port) (y-or-n-p "Connect using TLS instead? ") (setq port erc-default-port-tls))) #'erc-open-tls-stream)) @@ -2891,7 +2895,8 @@ and defers to `erc-compute-port', `erc-compute-user', and ;;;###autoload (cl-defun erc-tls (&key (server (erc-compute-server)) - (port (erc-compute-port 'ircs-u)) + (port (let ((erc-default-port erc-default-port-tls)) + (erc-compute-port))) (nick (erc-compute-nick)) (user (erc-compute-user)) password @@ -8839,7 +8844,7 @@ Sets the buffer local variables: - `erc-server-current-nick'" (setq erc-session-connector erc-server-connect-function erc-session-server (erc-compute-server server) - erc-session-port (or port erc-default-port) + erc-session-port (erc-compute-port port) erc-session-user-full-name (erc-compute-full-name name) erc-session-username (erc-compute-user user) erc-session-password (erc--compute-server-password passwd nick)) @@ -8912,8 +8917,12 @@ non-nil value is found. - PORT (the argument passed to this function) - The `erc-port' option -- The `erc-default-port' variable" - (erc-normalize-port (or port erc-port erc-default-port))) +- The `erc-default-port' variable + +Note that between ERC 5.5 and 5.6.1, this function filtered its result +through `erc-normalize-port', which introduced regrettable surprises, +such as unwelcome, possibly null, type conversions." + (or (and port (not (equal "" port)) port) erc-port erc-default-port)) ;; time routines @@ -9873,9 +9882,8 @@ by `erc' and `erc-tls'." (or (eql 6697 (plist-get plist :port)) (yes-or-no-p "Connect using TLS? ")))) (erc-server (plist-get plist :server)) - (erc-port (or (plist-get plist :port) - (and ircsp (erc-normalize-port 'ircs-u)) - erc-port)) + (erc-default-port (if ircsp erc-default-port-tls erc-default-port)) + (erc-port (erc-compute-port (plist-get plist :port))) (erc-nick (or (plist-get plist :nick) erc-nick)) (erc-password (plist-get plist :password)) (args (erc-select-read-args))) @@ -9907,9 +9915,9 @@ Customize `erc-url-connect-function' to override this." (and (string-equal erc-session-server host) ;; Ports only matter when dialed hosts ;; match and we have sufficient info. - (or (not port) - (= (erc-normalize-port erc-session-port) - port))))))))) + (or (null port) + (erc-port-equal erc-session-port + port))))))))) key deferred) (unless server-buffer (setq deferred t diff --git a/test/lisp/erc/erc-scenarios-auth-source.el b/test/lisp/erc/erc-scenarios-auth-source.el index f0a7a4cbaca..7e1e7c2f3ab 100644 --- a/test/lisp/erc/erc-scenarios-auth-source.el +++ b/test/lisp/erc/erc-scenarios-auth-source.el @@ -44,15 +44,19 @@ (string-join ents "\n"))) (auth-sources (list netrc-file)) (auth-source-do-cache nil) + (erc-port (and (eq erc-port 'test) (number-to-string port))) (erc-scenarios-common-extra-teardown (lambda () - (delete-file netrc-file)))) + (delete-file netrc-file))) + ;; With a `cl-defun', a keyword's presence prevents the default + ;; init form from being evaluated, even if its value is nil. + (args `( :server "127.0.0.1" + ,@(and (null erc-port) (list :port port)) + :nick "tester" + :full-name "tester" + :id ,id))) (ert-info ("Connect") - (with-current-buffer (erc :server "127.0.0.1" - :port port - :nick "tester" - :full-name "tester" - :id id) + (with-current-buffer (apply #'erc args) (should (string= (buffer-name) (if id (symbol-name id) (format "127.0.0.1:%d" port)))) @@ -60,12 +64,13 @@ (ert-deftest erc-scenarios-base-auth-source-server--dialed () :tags '(:expensive-test) - (erc-scenarios-common--auth-source - nil 'foonet - "machine GNU.chat port %d user tester password fake" - "machine FooNet port %d user tester password fake" - "machine 127.0.0.1 port %d user tester password changeme" - "machine 127.0.0.1 port %d user imposter password fake")) + (let ((erc-port 'test)) + (erc-scenarios-common--auth-source + nil 'foonet + "machine GNU.chat port %d user tester password fake" + "machine FooNet port %d user tester password fake" + "machine 127.0.0.1 port \"%s\" user tester password changeme" ; correct + "machine 127.0.0.1 port %d user imposter password fake"))) (ert-deftest erc-scenarios-base-auth-source-server--netid () :tags '(:expensive-test) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 7a2fea2c168..d77d8588dad 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -3057,6 +3057,22 @@ (should-not (buffer-live-p spam-buffer)) (kill-buffer chan-buffer))) +(ert-deftest erc-normalize-port () + ;; The empty string, nil, and unsupported types become nil. + (should-not (erc-normalize-port "")) + (should-not (erc-normalize-port nil)) + (should-not (erc-normalize-port (current-buffer))) + + ;; Unrecognized names are coerced to 0. + (should (equal 0 (erc-normalize-port "fake"))) + + ;; Numbers pass through, but numeric strings are coerced. + (should (equal 6667 (erc-normalize-port 6667))) + (should (equal 6697 (erc-normalize-port "6697"))) + + ;; Strange IANA mappings recognized. + (should (equal 6665 (erc-normalize-port "ircu")))) + (defvar erc-tests--ipv6-examples '("1:2:3:4:5:6:7:8" "::ffff:10.0.0.1" "::ffff:1.2.3.4" "::ffff:0.0.0.0" commit d899519221b23a895672a100f63410ffb8b8b1e4 Author: Trevor Arjeski Date: Sun Nov 24 23:35:41 2024 +0300 Allow querying auth-source with port as string in ERC * lisp/erc/erc.el (erc--auth-source-determine-params-defaults): Allow arbitrary strings for `ers-session-port'. Previously, if a port/service was any string other than "irc", the auth-source query would fail for a seemingly unknown reason. Restricting the value to "irc" is unnecessary since "irc" is already added to the list of ports, and `make-network-process' already consults /etc/services for well-known service names, like "ircs-u", etc. This change allows a user to (setopt erc-port "1234"), intentionally or accidentally, while still being able to use .authinfo for password management. (Bug#74516) Copyright-paperwork-exempt: yes diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index e77cb02819d..ab31696b669 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -4674,8 +4674,9 @@ node `(erc) auth-source'." (list net erc-server-announced-name erc-session-server))) (ports (list (cl-typecase erc-session-port (integer (number-to-string erc-session-port)) - (string (and (string= erc-session-port "irc") - erc-session-port)) ; or nil + (string (and (not (member erc-session-port + '("" "irc"))) + erc-session-port)) (t erc-session-port)) "irc"))) (list (cons :host (delq nil hosts)) commit e0d2c6f20f01366c41ba33d663a2b319dd9b7ab1 Author: F. Jason Park Date: Sun Nov 24 15:30:02 2024 -0800 Clear buffer-undo-list after sending input in ERC * lisp/erc/erc.el (erc-insert-line): Assume `erc-insert-marker' points somewhere. (erc-send-current-line): Set `buffer-undo-list' to nil because it should only record editing changes in the prompt area, which has just been cleared. ERC did this via `erc-display-prompt' prior to 5.6, but it now leaves the prompt alone by default. * test/lisp/erc/erc-tests.el (erc-update-undo-list): New test. (Bug#74518) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index 7028d0a68cc..e77cb02819d 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -3515,8 +3515,7 @@ modification hooks)." (add-text-properties (point-min) (1+ (point-min)) props))) (erc--refresh-prompt))))) (run-hooks 'erc-insert-done-hook) - (erc-update-undo-list (- (or (marker-position (or erc--insert-marker - erc-insert-marker)) + (erc-update-undo-list (- (or erc--insert-marker erc-insert-marker (point-max)) insert-position)))))) @@ -8200,10 +8199,9 @@ ERC prints them as a single message joined by newlines.") ;; Fix the buffer if the command didn't kill it (when (buffer-live-p old-buf) (with-current-buffer old-buf - (save-restriction - (widen) - (let ((buffer-modified (buffer-modified-p))) - (set-buffer-modified-p buffer-modified)))))) + (setq buffer-undo-list nil) + ;; `set-buffer-modified-p' used to do this here. + (force-mode-line-update)))) ;; Only when last hook has been run... (run-hook-with-args 'erc-send-completed-hook str))) diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 4c5521721f0..7a2fea2c168 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -2915,6 +2915,85 @@ (should (equal (erc-tests--format-my-nick "oh my") expect)) (should (equal (erc--format-speaker-input-message "oh my") expect)))) +(ert-deftest erc-update-undo-list () + ;; Remove `stamp' so this can run in any locale. Alternatively, we + ;; could explicitly enable it and bind its format options to strings + ;; that lack specifiers (perhaps in a separate test). + (let ((erc-modules (remq 'stamp erc-modules)) + (erc-mode-hook erc-mode-hook) + (erc-insert-modify-hook erc-insert-modify-hook) + (erc-send-modify-hook erc-send-modify-hook) + (inhibit-message noninteractive) + marker) + + (erc-stamp-mode -1) + (erc-tests-common-make-server-buf) + (setq erc-server-current-nick "tester") + + (with-current-buffer (erc--open-target "#chan") + ;; Add some filler to simulate more realistic values. + (erc-tests-common-simulate-line + ":irc.foonet.org 353 tester = #chan :bob tester alice") + (erc-tests-common-simulate-line + ":irc.foonet.org 366 tester #chan :End of NAMES list") + (should (erc-get-server-user "bob")) + + (goto-char (point-max)) + (should (= (point) 45)) + + ;; Populate undo list with contrived values. + (let ((kill-ring (list "abc")) + interprogram-paste-function) + (yank)) + (push nil buffer-undo-list) + (push (point-max) buffer-undo-list) + (setq marker (point-marker)) + (put-text-property 46 47 'face 'warning) + (call-interactively #'delete-backward-char 1) + (push nil buffer-undo-list) + (should (= (point) 47)) + (should (equal buffer-undo-list `(nil + ("c" . -47) + (,marker . -1) + (nil face nil 46 . 47) + 48 + nil + (45 . 48)))) + + ;; The first char after the prompt is at buffer pos 45. + (should (= 40 (- 45 (length (erc-prompt))) erc-insert-marker)) + + ;; A new message arrives, growing the buffer by 11 chars. + (erc-tests-common-simulate-privmsg "bob" "test") + (should (equal (buffer-substring 40 erc-insert-marker) " test\n")) + (should (= (point-max) 58)) + (should (= 11 (length " test\n") (- (point) 47))) + + ;; The list remains unchanged relative to the end of the buffer. + (should (equal buffer-undo-list `(nil + ("c" . -58) + (,marker . -1) + (nil face nil 57 . 58) + 59 + nil + (56 . 59)))) + + ;; Undo behavior works as expected. + (undo nil) + (should (erc-tests-common-equal-with-props + (buffer-substring erc-input-marker (point-max)) + #("abc" 1 2 (face nil)))) + (should (equal (take 4 buffer-undo-list) + `((nil face warning 57 . 58) + (58 . 59) + nil + ("c" . -58)))) + (undo 2) + (should (string-empty-p (erc-user-input))))) + + (when noninteractive + (erc-tests-common-kill-buffers))) + (ert-deftest erc--route-insertion () (erc-tests-common-prep-for-insertion) (erc-tests-common-init-server-proc "sleep" "1") commit b5f1a9e6d3e565585352747733c73d45e6be3bda Author: Alan Mackenzie Date: Sun Dec 1 17:27:34 2024 +0000 CC Mode: Optimize scanning of heavily nested brace blocks This should have fixed bug#74357. * lisp/progmodes/cc-engine.el (c-laomib-cache): Change the size of this cache from 4 to 50. (c-laomib-get-cache, c-laomib-put-cache): Use assq, memq, and ntake or butlast, rather than looping through the cache with a cdr loop. No longer attempt to preserve the "largest" cache element. (c-looking-at-or-maybe-in-bracelist): Extend the range covered by a c-laomib-cache element, rather than adding a second element to the cache. (c-no-bracelist-cache): A new cache for the use of c-inside-bracelist-p, based on the c-parse-state cache. (c-inside-bracelist-p): Use the new cache to call c-looking-at-or-maybe-in-bracelist less often. * lisp/progmodes/cc-mode.el (c-basic-common-init): Initialize c-no-bracelist-cache. (c-before-change): Invalidate c-no-bracelist-cache. diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index c46cd54438b..d880cdabaaa 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -13056,13 +13056,10 @@ comment at the start of cc-engine.el for more info." ;; Return that element or nil if one wasn't found. (let ((ptr c-laomib-cache) elt) - (while - (and ptr - (setq elt (car ptr)) - (or (not (eq (car elt) containing-sexp)) - (< start (car (cddr elt))))) - (setq ptr (cdr ptr))) - (when ptr + (while (and (setq elt (assq containing-sexp ptr)) + (< start (car (cddr elt)))) + (setq ptr (cdr (memq elt ptr)))) + (when elt ;; Move the fetched `elt' to the front of the cache. (setq c-laomib-cache (delq elt c-laomib-cache)) (push elt c-laomib-cache) @@ -13076,46 +13073,24 @@ comment at the start of cc-engine.el for more info." (when lim (let (old-elt (new-elt (list lim start end result)) - big-ptr (cur-ptr c-laomib-cache) - togo (size 0) cur-size) + size) ;; If there is an elt which overlaps with the new element, remove it. - (while - (and cur-ptr - (setq old-elt (car cur-ptr)) - (or (not (eq (car old-elt) lim)) - (not (and (> start (car (cddr old-elt))) - (<= start (cadr old-elt)))))) - (setq cur-ptr (cdr cur-ptr))) + (while (and (setq old-elt (assq lim cur-ptr)) + (not (and (> start (car (cddr old-elt))) + (<= start (cadr old-elt))))) + (setq cur-ptr (cdr (memq old-elt cur-ptr)))) (when (and cur-ptr old-elt) (setq c-laomib-cache (delq old-elt c-laomib-cache))) - (while (>= (length c-laomib-cache) 4) - ;; We delete the least recently used elt which doesn't enclose START, - ;; or ... - (dolist (elt c-laomib-cache) - (if (or (<= start (cadr elt)) - (> start (car (cddr elt)))) - (setq togo elt))) - - ;; ... delete the least recently used elt which isn't the biggest. - (when (not togo) - (setq cur-ptr c-laomib-cache) - (while (cdr cur-ptr) - (setq cur-size (- (cadr (cadr cur-ptr)) - (car (cddr (cadr cur-ptr))))) - (when (> cur-size size) - (setq size cur-size - big-ptr cur-ptr)) - (setq cur-ptr (cdr cur-ptr))) - (setq togo (if (cddr big-ptr) - (car (last big-ptr)) - (car big-ptr)))) - - (setq c-laomib-cache (delq togo c-laomib-cache))) - - (push new-elt c-laomib-cache)))) + ;; Don't let the cache grow indefinitely. + (cond + ((fboundp 'ntake) ; >= Emacs 29.1 + (setq c-laomib-cache (ntake 49 c-laomib-cache))) + ((>= (setq size (length c-laomib-cache)) 50) + (setq c-laomib-cache (butlast c-laomib-cache (- size 49))))) + (push new-elt c-laomib-cache)))) (defun c-laomib-fix-elt (lwm elt paren-state) ;; Correct a c-laomib-cache entry ELT with respect to buffer changes, either @@ -13162,7 +13137,7 @@ comment at the start of cc-engine.el for more info." (setq c-laomib-cache (delq elt c-laomib-cache))))))) (defun c-looking-at-or-maybe-in-bracelist (&optional containing-sexp lim) - ;; Point is at an open brace. If this starts a brace list, return a list + ;; Point is at an open brace. If this starts a brace list, return a cons ;; whose car is the buffer position of the start of the construct which ;; introduces the list, and whose cdr is the symbol `in-paren' if the brace ;; is directly enclosed in a parenthesis form (i.e. an arglist), t if we @@ -13301,10 +13276,7 @@ comment at the start of cc-engine.el for more info." (setq sub-bassign-p (c-laomib-loop lim2)) (if (<= (point) (cadr cache-entry)) (progn - (c-laomib-put-cache containing-sexp - start (nth 2 cache-entry) - (nth 3 cache-entry) ;; sub-bassign-p - ) + (setcar (cdr cache-entry) start) (setq braceassignp (nth 3 cache-entry)) (goto-char (nth 2 cache-entry))) (c-laomib-put-cache containing-sexp @@ -13395,14 +13367,20 @@ comment at the start of cc-engine.el for more info." (t t)))) ;; The caller can go up one level. )))) +;; A list of the form returned by `c-parse-state'. Each opening brace in it +;; is not the brace of a brace list. Any cons items in it are ignored, and +;; are also unreliable. +(defvar c-no-bracelist-cache nil) +(make-variable-buffer-local 'c-no-bracelist-cache) + (defun c-inside-bracelist-p (containing-sexp paren-state accept-in-paren) - ;; return the buffer position of the beginning of the brace list statement + ;; Return the buffer position of the beginning of the brace list statement ;; if CONTAINING-SEXP is inside a brace list, otherwise return nil. ;; - ;; CONTAINING-SEXP is the buffer pos of the innermost containing paren. NO - ;; IT ISN'T!!! [This function is badly designed, and probably needs - ;; reformulating without its first argument, and the critical position being - ;; at point.] + ;; CONTAINING-SEXP must be at an open brace, and is the buffer pos of the + ;; innermost containing brace. NO IT ISN'T!!! [This function is badly + ;; designed, and probably needs reformulating without its first argument, + ;; and the critical position being at point.] ;; ;; PAREN-STATE is the remainder of the state of enclosing braces. ;; ACCEPT-IN-PAREN is non-nil iff we will accept as a brace list a brace @@ -13416,32 +13394,55 @@ comment at the start of cc-engine.el for more info." ;; speed. ;; ;; This function might do hidden buffer changes. - ;; this will pick up array/aggregate init lists, even if they are nested. - (save-excursion - (let ((bufpos t) - next-containing) - (while (and (eq bufpos t) - containing-sexp) - (when paren-state - (setq next-containing (c-pull-open-brace paren-state))) - - (goto-char containing-sexp) - (if (c-looking-at-inexpr-block next-containing next-containing) - ;; We're in an in-expression block of some kind. Do not - ;; check nesting. We deliberately set the limit to the - ;; containing sexp, so that c-looking-at-inexpr-block - ;; doesn't check for an identifier before it. - (setq bufpos nil) - (if (not (eq (char-after) ?{)) - (setq bufpos nil) - (when (eq (setq bufpos (c-looking-at-or-maybe-in-bracelist - next-containing next-containing)) - t) - (setq containing-sexp next-containing - next-containing nil))))) - (and (consp bufpos) - (or accept-in-paren (not (eq (cdr bufpos) 'in-paren))) - (car bufpos))))) + ;; It will pick up array/aggregate init lists, even if they are nested. + (save-excursion + (let ((bufpos t) + next-containing non-brace-pos + (whole-paren-state (cons containing-sexp paren-state)) + (current-brace containing-sexp)) + (while (and (eq bufpos t) + current-brace + (not (memq current-brace c-no-bracelist-cache))) + (setq next-containing + (and paren-state (c-pull-open-brace paren-state))) + (goto-char current-brace) + (cond + ((c-looking-at-inexpr-block next-containing next-containing) + ;; We're in an in-expression block of some kind. Do not + ;; check nesting. We deliberately set the limit to the + ;; containing sexp, so that c-looking-at-inexpr-block + ;; doesn't check for an identifier before it. + (setq bufpos nil)) + ((not (eq (char-after) ?{)) + (setq non-brace-pos (point)) + (setq bufpos nil)) + ((eq (setq bufpos (c-looking-at-or-maybe-in-bracelist + next-containing next-containing)) + t) + (setq current-brace next-containing)))) + (cond + ((consp bufpos) + (and (or accept-in-paren (not (eq (cdr bufpos) 'in-paren))) + (car bufpos))) + (non-brace-pos + ;; We've encountered a ( or a [. Remove the "middle part" of + ;; paren-state, the part that isn't non-brace-list braces, to get the + ;; new value of `c-no-bracelist-cache'. + (setq whole-paren-state + ;; `c-whack-state-before' makes a copy of `whole-paren-state'. + (c-whack-state-before (1+ non-brace-pos) whole-paren-state)) + (while (and next-containing + (not (memq next-containing c-no-bracelist-cache))) + (setq next-containing (c-pull-open-brace paren-state))) + (setq c-no-bracelist-cache + (nconc whole-paren-state + (and next-containing (list next-containing)) + paren-state)) + nil) + ((not (memq containing-sexp c-no-bracelist-cache)) + ;; Update `c-no-bracelist-cache' + (setq c-no-bracelist-cache (copy-tree whole-paren-state)) + nil))))) (defun c-looking-at-special-brace-list () ;; If we're looking at the start of a pike-style list, i.e., `({ })', diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index c5bb075c7f6..6676219e702 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el @@ -656,6 +656,8 @@ that requires a literal mode spec at compile time." ;; Initialize the cache for `c-looking-at-or-maybe-in-bracelist'. (setq c-laomib-cache nil) + ;; Initialize the cache for non brace-list braces. + (setq c-no-bracelist-cache nil) ;; Initialize the three literal sub-caches. (c-truncate-lit-pos/state-cache 1) ;; Initialize the cache of brace pairs, and opening braces/brackets/parens. @@ -2337,7 +2339,9 @@ with // and /*, not more generic line and block comments." ;; The following must happen after the previous, which likely alters ;; the macro cache. (when c-opt-cpp-symbol - (c-invalidate-macro-cache beg end))))) + (c-invalidate-macro-cache beg end)) + (setq c-no-bracelist-cache + (c-whack-state-after beg c-no-bracelist-cache))))) (defvar c-in-after-change-fontification nil) (make-variable-buffer-local 'c-in-after-change-fontification) commit 2bcf0b3d099ba6f059a66bcdf522b277f98e9ea3 Author: Manuel Giraud Date: Sun Dec 1 17:25:24 2024 +0100 Fix last change in tutorial.el * lisp/tutorial.el (tutorial--point-after-chkeys): Make it permanent-local. (Bug#74636) diff --git a/lisp/tutorial.el b/lisp/tutorial.el index 86537d995fe..f98aff167d2 100644 --- a/lisp/tutorial.el +++ b/lisp/tutorial.el @@ -655,6 +655,7 @@ with some explanatory links." (put 'tutorial--starting-point 'permanent-local t) (put 'tutorial--lang 'permanent-local t) (put 'tutorial--point-before-chkeys 'permanent-local t) +(put 'tutorial--point-after-chkeys 'permanent-local t) (defun tutorial--save-on-kill () "Query the user about saving the tutorial when killing Emacs." commit 748b19e56e87fab44cb5474613502f8e96064a46 Author: Manuel Giraud Date: Sun Dec 1 13:50:05 2024 +0100 Update to version 2.58 of librsvg API (bug#74606) * src/image.c (init_svg_functions): Declare new function. (svg_load_image): Use it. diff --git a/src/image.c b/src/image.c index 0b590faab76..ed680be54dd 100644 --- a/src/image.c +++ b/src/image.c @@ -11655,7 +11655,11 @@ DEF_DLL_FN (void, rsvg_handle_get_dimensions, DEF_DLL_FN (gboolean, rsvg_handle_set_stylesheet, (RsvgHandle *, const guint8 *, gsize, GError **)); # endif +# if LIBRSVG_CHECK_VERSION (2, 58, 0) +DEF_DLL_FN (GdkPixbuf *, rsvg_handle_get_pixbuf_and_error, (RsvgHandle *, GError **)); +# else DEF_DLL_FN (GdkPixbuf *, rsvg_handle_get_pixbuf, (RsvgHandle *)); +# endif DEF_DLL_FN (int, gdk_pixbuf_get_width, (const GdkPixbuf *)); DEF_DLL_FN (int, gdk_pixbuf_get_height, (const GdkPixbuf *)); DEF_DLL_FN (guchar *, gdk_pixbuf_get_pixels, (const GdkPixbuf *)); @@ -11714,8 +11718,11 @@ init_svg_functions (void) #if LIBRSVG_CHECK_VERSION (2, 48, 0) LOAD_DLL_FN (library, rsvg_handle_set_stylesheet); #endif +#if LIBRSVG_CHECK_VERSION (2, 58, 0) + LOAD_DLL_FN (library, rsvg_handle_get_pixbuf_and_error); +#else LOAD_DLL_FN (library, rsvg_handle_get_pixbuf); - +#endif LOAD_DLL_FN (gdklib, gdk_pixbuf_get_width); LOAD_DLL_FN (gdklib, gdk_pixbuf_get_height); LOAD_DLL_FN (gdklib, gdk_pixbuf_get_pixels); @@ -11760,7 +11767,11 @@ init_svg_functions (void) # if LIBRSVG_CHECK_VERSION (2, 48, 0) # undef rsvg_handle_set_stylesheet # endif -# undef rsvg_handle_get_pixbuf +# if LIBRSVG_CHECK_VERSION (2, 58, 0) +# undef rsvg_handle_get_pixbuf_and_error +# else +# undef rsvg_handle_get_pixbuf +# endif # if LIBRSVG_CHECK_VERSION (2, 32, 0) # undef g_file_new_for_path # undef g_memory_input_stream_new_from_data @@ -11801,7 +11812,11 @@ init_svg_functions (void) # if LIBRSVG_CHECK_VERSION (2, 48, 0) # define rsvg_handle_set_stylesheet fn_rsvg_handle_set_stylesheet # endif -# define rsvg_handle_get_pixbuf fn_rsvg_handle_get_pixbuf +# if LIBRSVG_CHECK_VERSION (2, 58, 0) +# define rsvg_handle_get_pixbuf_and_error fn_rsvg_handle_get_pixbuf_and_error +# else +# define rsvg_handle_get_pixbuf fn_rsvg_handle_get_pixbuf +# endif # if LIBRSVG_CHECK_VERSION (2, 32, 0) # define g_file_new_for_path fn_g_file_new_for_path # define g_memory_input_stream_new_from_data \ @@ -12306,8 +12321,13 @@ svg_load_image (struct frame *f, struct image *img, char *contents, /* We can now get a valid pixel buffer from the svg file, if all went ok. */ +#if LIBRSVG_CHECK_VERSION (2, 58, 0) + pixbuf = rsvg_handle_get_pixbuf_and_error (rsvg_handle, &err); + if (err) goto rsvg_error; +#else pixbuf = rsvg_handle_get_pixbuf (rsvg_handle); if (!pixbuf) goto rsvg_error; +#endif g_object_unref (rsvg_handle); xfree (wrapped_contents); commit 4c67f636c08b6190bb5ab8953d1956b3862a9fb1 Author: Visuwesh Date: Sun Dec 1 11:54:11 2024 +0530 Fix decoding of non-ASCII email attachments * lisp/mail/rfc2231.el (rfc2231-parse-string): Fix logic when a non-ASCII file name is split between two filename*N* parts. (Bug#74624) diff --git a/lisp/mail/rfc2231.el b/lisp/mail/rfc2231.el index 33324cafb5b..632e270a922 100644 --- a/lisp/mail/rfc2231.el +++ b/lisp/mail/rfc2231.el @@ -193,7 +193,7 @@ must never cause a Lisp error." (push (list attribute value encoded) cparams)) ;; Repetition of a part; do nothing. ((and elem - (null number)) + (null part)) ) ;; Concatenate continuation parts. (t commit bd8a6f70fb947c4ea11c4772cff6e81180e5e35a Author: Stephen Berman Date: Sat Nov 30 23:28:06 2024 +0100 Prevent "Selecting deleted buffer" error with dabbrev-expand * lisp/dabbrev.el (dabbrev-expand): Use the buffer where the last expansion was found only if it is still a live buffer (bug#74090). * test/lisp/dabbrev-tests.el (dabbrev-expand-test-minibuffer-3): Fix typo in doc string. (dabbrev-expand-after-killing-buffer): New test. diff --git a/lisp/dabbrev.el b/lisp/dabbrev.el index bbe6a64b626..84306fb3ae7 100644 --- a/lisp/dabbrev.el +++ b/lisp/dabbrev.el @@ -472,8 +472,10 @@ See also `dabbrev-abbrev-char-regexp' and \\[dabbrev-completion]." ;; minibuffer. (window-buffer (get-mru-window))) ;; Otherwise, if we found the expansion in another - ;; buffer, use that buffer for further expansions. - (dabbrev--last-buffer-found dabbrev--last-buffer-found) + ;; buffer and that buffer is still live, use that + ;; buffer for further expansions. + ((buffer-live-p dabbrev--last-buffer-found) + dabbrev--last-buffer-found) ;; Otherwise, use the buffer where we invoked ;; dabbrev-expand. (t (current-buffer)))) diff --git a/test/lisp/dabbrev-tests.el b/test/lisp/dabbrev-tests.el index 987106aa5af..b5737373875 100644 --- a/test/lisp/dabbrev-tests.el +++ b/test/lisp/dabbrev-tests.el @@ -238,7 +238,7 @@ entered." ;; FIXME: Why is dabbrev--reset-global-variables needed here? (ert-deftest dabbrev-expand-test-minibuffer-3 () "Test replacing an expansion in the minibuffer using two buffers. -The first expansion should befound in the buffer from which the +The first expansion should be found in the buffer from which the minibuffer was entered, the replacement should found in another buffer." (with-dabbrev-test (find-file (ert-resource-file "INSTALL_BEGIN")) @@ -275,4 +275,36 @@ minibuffer was entered, the replacement should found in another buffer." (should (string= (minibuffer-contents) "Indic and")) (delete-minibuffer-contents)))) +(ert-deftest dabbrev-expand-after-killing-buffer () + "Test expansion after killing buffer containing first expansion. +Finding successive expansions in another live buffer should succeed, but +after killing the buffer, expansion should fail with a user-error." + ;; FIXME? The message shown by the user-error is in *Messages* but + ;; since the test finishes on hitting the user-error, we cannot test + ;; further, either for the content of the message or the content of + ;; the current buffer, so apparently cannot reproduce what a user + ;; entering these commands manually sees. + (with-dabbrev-test + (with-current-buffer (get-buffer-create "foo") + (insert "abc abd")) + (switch-to-buffer "*scratch*") + (erase-buffer) + (execute-kbd-macro (kbd "ab M-/")) + (should (string= (buffer-string) "abc")) + (execute-kbd-macro (kbd "SPC ab M-/")) + (should (string= (buffer-string) "abc abc")) + (erase-buffer) + (execute-kbd-macro (kbd "abc SPC ab M-/ M-/")) + (should (string= (buffer-string) "abc abd")) + (kill-buffer "foo") + (erase-buffer) + (should-error (execute-kbd-macro (kbd "abc SPC ab M-/ M-/")) + :type 'user-error) + ;; (should (string= (buffer-string) "abc abc")) + ;; (with-current-buffer "*Messages*" + ;; (goto-char (point-max)) + ;; (should (string= (buffer-substring (pos-bol) (pos-eol)) + ;; "No further dynamic expansion for ‘ab’ found"))) + )) + ;;; dabbrev-tests.el ends here commit 0a753603a53622d939661d796eb203908867bb9e Author: Eshel Yaron Date: Sat Nov 30 12:56:49 2024 +0100 ; (dictionary-search-interface): Fix bug#74511. * lisp/net/dictionary.el (dictionary-search-interface): During initialization, do not override individual customization of the other options that this option affects (by applying the :set function), unless this option was explicitly set. diff --git a/lisp/net/dictionary.el b/lisp/net/dictionary.el index f17dc160997..9c932c0c6d5 100644 --- a/lisp/net/dictionary.el +++ b/lisp/net/dictionary.el @@ -317,6 +317,7 @@ Otherwise, `dictionary-search' displays definitions in a *Dictionary* buffer." dictionary-read-dictionary-function) vals)) (set-default-toplevel-value symbol value)) + :initialize #'custom-initialize-changed :version "30.1") (defface dictionary-word-definition-face