From: Vincenzo Pupillo <v.pupillo@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 73779@debbugs.gnu.org
Subject: bug#73779: 30.0.91; [PATCH] php-ts-mode: Better indentation and font locking, support for the latest grammar version.
Date: Sat, 19 Oct 2024 14:13:33 +0200 [thread overview]
Message-ID: <4928661.OV4Wx5bFTl@fedora> (raw)
In-Reply-To: <86h699oqe6.fsf@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 534 bytes --]
> I was about to install this, but byte-compiling the modified file
> yields the following warnings:
>
> ELC progmodes/php-ts-mode.elc
>
> In php-ts-mode--open-statement-group-heuristic:
> progmodes/php-ts-mode.el:435:58: Warning: Unused lexical argument `parent'
> progmodes/php-ts-mode.el:499:50: Warning: argument `_bol' not left unused
> progmodes/php-ts-mode.el:518:56: Warning: argument `_bol' not left unused
>
> Could you please fix these and resubmit the patch?
Sorry Eli, it's okay now.
Thank you.
Vincenzo
[-- Attachment #2: 0001-Fix-php-ts-mode-better-indentation-and-font-locking.patch --]
[-- Type: text/x-patch, Size: 14959 bytes --]
From fdf80d7e8db62ad17c70fc52eebd531c7d504206 Mon Sep 17 00:00:00 2001
From: Vincenzo Pupillo <v.pupillo@gmail.com>
Date: Thu, 10 Oct 2024 16:06:37 +0200
Subject: [PATCH] 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
braceles 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.
---
lisp/progmodes/php-ts-mode.el | 170 ++++++++++++++++++++++------------
1 file changed, 113 insertions(+), 57 deletions(-)
diff --git a/lisp/progmodes/php-ts-mode.el b/lisp/progmodes/php-ts-mode.el
index d2559e5a45f..1c5fdb6f617 100644
--- a/lisp/progmodes/php-ts-mode.el
+++ b/lisp/progmodes/php-ts-mode.el
@@ -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 @@ php-ts-mode--syntax-table
\f
;;; 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 @@ php-ts-mode--parent-eol
(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)))))
-
-(defun php-ts-mode--parent-html-heuristic (node parent _bol &rest _)
+ (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 _)
"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 @@ php-ts-mode--indent-styles
((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 @@ php-ts-mode--indent-styles
;; 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 @@ php-ts-mode--indent-styles
(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 @@ php-ts-mode--predefined-constant
"__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 @@ php-ts-mode--font-lock-settings
: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 @@ php-ts-mode--font-lock-settings
(named_label_statement (name) @font-lock-constant-face))
:language 'php
- ;;:override t
:feature 'delimiter
`((["," ":" ";" "\\"]) @font-lock-delimiter-face)
@@ -850,7 +902,6 @@ php-ts-mode--font-lock-settings
: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 @@ php-ts-mode--font-lock-settings
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 @@ php-ts-mode--font-lock-settings
'((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))
--
2.47.0
next prev parent reply other threads:[~2024-10-19 12:13 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-10-12 20:12 bug#73779: 30.0.91; [PATCH] php-ts-mode: Better indentation and font locking, support for the latest grammar version Vincenzo Pupillo
2024-10-13 4:59 ` Eli Zaretskii
2024-10-13 9:19 ` Vincenzo Pupillo
2024-10-18 6:51 ` Eli Zaretskii
2024-10-19 12:13 ` Vincenzo Pupillo [this message]
2024-10-19 13:13 ` Eli Zaretskii
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4928661.OV4Wx5bFTl@fedora \
--to=v.pupillo@gmail.com \
--cc=73779@debbugs.gnu.org \
--cc=eliz@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).