1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-21 06:55:39 +00:00

Fix 'php-ts-mode': better indentation and font locking

Incomplete compound_statement or colon_block (statement-group
without a closing brace or closing keyword) that are not inside
a function or method are not recognized as such by tree-sitter-php.
A new function 'php-ts-mode--open-statement-group-heuristic'
handles this case.  Font locking of magic methods and better
support for alternative control structure syntax.
Support for latest grammar version.
* lisp/progmodes/php-ts-mode.el
(php-ts-mode--language-source-alist): Updated grammar version.
(php-ts-mode--possibly-braceless-keyword-re): Regular expression
for braceless keyword.
(php-ts-mode--open-statement-group-heuristic): New function.
(php-ts-mode--parent-html-bol): Use the new function and doc fix.
(php-ts-mode--parent-html-heuristic): Use the new function and doc
fix.
(php-ts-mode--indent-styles): Use the new function and add
'colon_block' support.
(php-ts-mode--class-magic-methods): New predefined magic methods
list.
(php-ts-mode--test-namespace-name-as-prefix-p): Doc fix.
(php-ts-mode--test-namespace-aliasing-clause-p): Fix the test and
doc.
(php-ts-mode--test-namespace-use-group-clause-p): Doc fix.
(php-ts-mode--test-visibility-modifier-operation-clause-p): New
function for the new asymmetric property visibility feature of
PHP 8.4.
(php-ts-mode--font-lock-settings): Font lock for class magic methods
and alternative syntax.  Better font lock for 'instanceof'.  Use
'font-lock-function-call-face' for scoped and member call expression.
(bug#73779)
This commit is contained in:
Vincenzo Pupillo 2024-10-10 16:06:37 +02:00 committed by Eli Zaretskii
parent 902029b141
commit 4e0cb960db

View File

@ -84,7 +84,7 @@
;;; Install treesitter language parsers
(defvar php-ts-mode--language-source-alist
'((php . ("https://github.com/tree-sitter/tree-sitter-php" "v0.23.0" "php/src"))
'((php . ("https://github.com/tree-sitter/tree-sitter-php" "v0.23.4" "php/src"))
(phpdoc . ("https://github.com/claytonrcarter/tree-sitter-phpdoc"))
(html . ("https://github.com/tree-sitter/tree-sitter-html" "v0.23.0"))
(javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.23.0"))
@ -428,6 +428,27 @@ Useful for debugging."
;;; Indent
(defconst php-ts-mode--possibly-braceless-keyword-re
(regexp-opt '("if" "for" "foreach" "while" "do") 'symbols)
"Regexp matching keywords optionally followed by an opening brace.")
(defun php-ts-mode--open-statement-group-heuristic (node _parent bol &rest _)
"Heuristic matcher for statement-group without closing bracket.
Return `php-ts-mode-indent-offset' plus 1 when BOL is after
`php-ts-mode--possibly-braceless-keyword-re', otherwise return 0. It's
usefull for matching incomplete compound_statement or colon_block.
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)
(if (re-search-forward
php-ts-mode--possibly-braceless-keyword-re
bol t)
(+ 1 php-ts-mode-indent-offset)
0))))
;; taken from c-ts-mode
(defun php-ts-mode--else-heuristic (node parent bol &rest _)
"Heuristic matcher for when \"else\" is followed by a closing bracket.
@ -475,43 +496,50 @@ NODE is the node to match and PARENT is its parent."
(goto-char (treesit-node-start parent))
(line-end-position)))
(defun php-ts-mode--parent-html-bol (node parent _bol &rest _)
(defun php-ts-mode--parent-html-bol (node parent bol &rest _)
"Find the first non-space characters of the HTML tags before NODE.
When NODE is nil call `php-ts-mode--open-statement-group-heuristic'.
PARENT is NODE's parent, BOL is the beginning of non-whitespace
characters of the current line."
(save-excursion
(let ((html-node (treesit-search-forward node "text" t)))
(if html-node
(let ((end-html (treesit-node-end html-node)))
(goto-char end-html)
(backward-word)
(back-to-indentation)
(point))
(treesit-node-start parent)))))
(if (null node)
;; If NODE is nil it could be an open statement-group.
(php-ts-mode--open-statement-group-heuristic node parent bol)
(save-excursion
(let ((html-node (treesit-search-forward node "text" t)))
(if html-node
(let ((end-html (treesit-node-end html-node)))
(goto-char end-html)
(backward-word)
(back-to-indentation)
(point))
(treesit-node-start parent))))))
(defun php-ts-mode--parent-html-heuristic (node parent _bol &rest _)
(defun php-ts-mode--parent-html-heuristic (node parent bol &rest _)
"Return position based on html indentation.
Returns 0 if the NODE is after the </html>, otherwise returns the
indentation point of the last word before the NODE, plus the
indentation offset. If there is no HTML tag, it returns the beginning
of the parent.
indentation point of the last word before the NODE, plus the indentation
offset. If there is no HTML tag, it returns the beginning of the
parent. When NODE is nil call `php-ts-mode--open-statement-group-heuristic'.
It can be used when you want to indent PHP code relative to the HTML.
PARENT is NODE's parent, BOL is the beginning of non-whitespace
characters of the current line."
(let ((html-node (treesit-search-forward node "text" t)))
(if html-node
(let ((end-html (treesit-node-end html-node)))
(save-excursion
(goto-char end-html)
(backward-word)
(back-to-indentation)
(if (search-forward "</html>" end-html t 1)
0
(+ (point) php-ts-mode-indent-offset))))
;; Maybe it's better to use bol?
(treesit-node-start parent))))
(if (null node)
;; If NODE is nil it could be an open statement-group.
(php-ts-mode--open-statement-group-heuristic node parent bol)
(let ((html-node (treesit-search-forward node "text" t)))
(if html-node
(let ((end-html (treesit-node-end html-node)))
(save-excursion
(goto-char end-html)
(backward-word)
(back-to-indentation)
(if (search-forward "</html>" end-html t 1)
0
(+ (point) php-ts-mode-indent-offset))))
;; Maybe it's better to use bol?
(treesit-node-start parent)))))
(defun php-ts-mode--array-element-heuristic (_node parent _bol &rest _)
"Return of the position of the first element of the array.
@ -648,16 +676,22 @@ characters of the current line."
((parent-is "initializer_list") parent-bol php-ts-mode-indent-offset)
;; Statement in {} blocks.
((or (and (parent-is "compound_statement")
((or (and (or (parent-is "compound_statement")
(parent-is "colon_block"))
;; If the previous sibling(s) are not on their
;; own line, indent as if this node is the first
;; sibling
php-ts-mode--first-sibling)
(match null "compound_statement"))
(or (match null "compound_statement")
(match null "colon_block")))
standalone-parent php-ts-mode-indent-offset)
((parent-is "compound_statement") parent-bol php-ts-mode-indent-offset)
((or (parent-is "compound_statement")
(parent-is "colon_block"))
parent-bol php-ts-mode-indent-offset)
;; Opening bracket.
((node-is "compound_statement") standalone-parent php-ts-mode-indent-offset)
((or (node-is "compound_statement")
(node-is "colon_block"))
standalone-parent php-ts-mode-indent-offset)
((parent-is "match_block") parent-bol php-ts-mode-indent-offset)
((parent-is "switch_block") parent-bol 0)
@ -667,6 +701,7 @@ characters of the current line."
;; rule for PHP alternative syntax
((or (node-is "else_if_clause")
(node-is "endif")
(node-is "endfor")
(node-is "endforeach")
(node-is "endwhile"))
parent-bol 0)
@ -679,9 +714,13 @@ characters of the current line."
(parent-is "switch_statement")
(parent-is "case_statement")
(parent-is "empty_statement"))
parent-bol php-ts-mode-indent-offset))))
parent-bol php-ts-mode-indent-offset)
;; Workaround: handle "for" open statement group. Currently
;; the grammar handles it differently than other control structures.
(no-node php-ts-mode--open-statement-group-heuristic 0))))
`((psr2
((parent-is "program") parent-bol 0)
((parent-is "program") php-ts-mode--open-statement-group-heuristic 0)
((parent-is "text_interpolation") column-0 0)
((parent-is "function_call_expression") parent-bol php-ts-mode-indent-offset)
,@common)
@ -774,21 +813,32 @@ characters of the current line."
"__FUNCTION__" "__LINE__" "__METHOD__" "__NAMESPACE__" "__TRAIT__")
"PHP predefined constant.")
(defun php-ts-mode--test-namespace-name-as-prefix-p ()
"Return t if namespace_name_as_prefix keyword is a named node, nil otherwise."
(defconst php-ts-mode--class-magic-methods
'("__construct" "__destruct" "__call" "__callStatic" "__get" "__set"
"__isset" "__unset" "__sleep" "__wakeup" "__serialize" "__unserialize"
"__toString" "__invoke" "__set_state" "__clone" "__debugInfo")
"PHP predefined magic methods.")
(defun php-ts-mode--test-namespace-name-as-prefix-p ()
"Return t if namespace_name_as_prefix is a named node, nil otherwise."
(ignore-errors
(progn (treesit-query-compile 'php "(namespace_name_as_prefix)" t) t)))
(defun php-ts-mode--test-namespace-aliasing-clause-p ()
"Return t if namespace_name_as_prefix keyword is named node, nil otherwise."
(defun php-ts-mode--test-namespace-aliasing-clause-p ()
"Return t if namespace_aliasing_clause is a named node, nil otherwise."
(ignore-errors
(progn (treesit-query-compile 'php "(namespace_name_as_prefix)" t) t)))
(progn (treesit-query-compile 'php "(namespace_aliasing_clause)" t) t)))
(defun php-ts-mode--test-namespace-use-group-clause-p ()
"Return t if namespace_use_group_clause keyword is named node, nil otherwise."
"Return t if namespace_use_group_clause is a named node, nil otherwise."
(ignore-errors
(progn (treesit-query-compile 'php "(namespace_use_group_clause)" t) t)))
(defun php-ts-mode--test-visibility-modifier-operation-clause-p ()
"Return t if (visibility_modifier (operation)) is defined, nil otherwise."
(ignore-errors
(progn (treesit-query-compile 'php "(visibility_modifier (operation))" t) t)))
(defun php-ts-mode--font-lock-settings ()
"Tree-sitter font-lock settings."
(treesit-font-lock-rules
@ -796,7 +846,10 @@ characters of the current line."
:language 'php
:feature 'keyword
:override t
`([,@php-ts-mode--keywords] @font-lock-keyword-face)
`([,@php-ts-mode--keywords] @font-lock-keyword-face
,@(when (php-ts-mode--test-visibility-modifier-operation-clause-p)
'((visibility_modifier (operation) @font-lock-builtin-face)))
(var_modifier) @font-lock-builtin-face)
:language 'php
:feature 'comment
@ -826,7 +879,6 @@ characters of the current line."
(named_label_statement (name) @font-lock-constant-face))
:language 'php
;;:override t
:feature 'delimiter
`((["," ":" ";" "\\"]) @font-lock-delimiter-face)
@ -850,7 +902,6 @@ characters of the current line."
:language 'php
:feature 'string
;;:override t
`(("\"") @font-lock-string-face
(encapsed_string) @font-lock-string-face
(string_content) @font-lock-string-face
@ -892,32 +943,37 @@ characters of the current line."
name: (_) @font-lock-type-face)
(trait_declaration
name: (_) @font-lock-type-face)
(property_declaration
(visibility_modifier) @font-lock-keyword-face)
(property_declaration
(var_modifier) @font-lock-keyword-face)
(enum_declaration
name: (_) @font-lock-type-face)
(function_definition
name: (_) @font-lock-function-name-face)
(method_declaration
name: (_) @font-lock-function-name-face)
(method_declaration
name: (name) @font-lock-builtin-face
(:match ,(rx-to-string
`(: bos (or ,@php-ts-mode--class-magic-methods) eos))
@font-lock-builtin-face))
("=>") @font-lock-keyword-face
(object_creation_expression
(name) @font-lock-type-face)
,@(when (php-ts-mode--test-namespace-name-as-prefix-p)
'((namespace_name_as_prefix "\\" @font-lock-delimiter-face)
(namespace_name_as_prefix
(namespace_name (name)) @font-lock-type-face)))
'((namespace_name_as_prefix "\\" @font-lock-delimiter-face)
(namespace_name_as_prefix
(namespace_name (name)) @font-lock-type-face)))
,@(if (php-ts-mode--test-namespace-aliasing-clause-p)
'((namespace_aliasing_clause (name) @font-lock-type-face))
'((namespace_use_clause alias: (name) @font-lock-type-face)))
'((namespace_aliasing_clause (name) @font-lock-type-face))
'((namespace_use_clause alias: (name) @font-lock-type-face)))
,@(when (not (php-ts-mode--test-namespace-use-group-clause-p))
'((namespace_use_group
(namespace_use_clause (name) @font-lock-type-face))))
'((namespace_use_group
(namespace_use_clause (name) @font-lock-type-face))))
(namespace_name "\\" @font-lock-delimiter-face)
(namespace_name (name) @font-lock-type-face)
(use_declaration (name) @font-lock-property-use-face))
(use_declaration (name) @font-lock-property-use-face)
(use_instead_of_clause (name) @font-lock-type-face)
(binary_expression
operator: "instanceof"
right: (name) @font-lock-type-face))
:language 'php
:feature 'function-scope
@ -932,9 +988,9 @@ characters of the current line."
'((function_call_expression
function: (name) @font-lock-function-call-face)
(scoped_call_expression
name: (_) @font-lock-function-name-face)
name: (_) @font-lock-function-call-face)
(member_call_expression
name: (_) @font-lock-function-name-face)
name: (_) @font-lock-function-call-face)
(nullsafe_member_call_expression
name: (_) @font-lock-constant-face))