Skip to content

Commit

Permalink
Fix some function call highlighting issues.
Browse files Browse the repository at this point in the history
This fixes a number of incorrect highlights surrounding function
calls.  Due to the ambiguity in the syntax tree, the function call
highlighting is not perfect, however a couple known highlighting
issues were fixed, including generic package instantiation, array
assignments, attribute function calls, etc.  Additionally, a
troubleshooting section was added for this topic to the documantation
to document the known issue and suggest workarounds.
  • Loading branch information
brownts committed Apr 26, 2024
1 parent a601374 commit a0c001f
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 9 deletions.
48 changes: 48 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,54 @@ setting:
(add-to-list 'org-src-lang-modes '("ada" . ada-ts)))
#+END_SRC

** Function Calls not Highlighted Correctly

If you observe places in the syntax highlighting where functions calls
are not being properly highlighted, such as an array being highlighted
as a function call, or a parameterless function call not being
highlighted, this is due to ambiguities in the syntax. From a pure
syntax perspective, array accesses look the same as function calls.
Also parameterless function calls look the same as variable accesses.
In places where it can be determined from the syntax (such as a
generic package instantiation) care is taken to avoid highlighting
these places as function calls. In other places, it cannot be known
from the syntax tree alone and that is where the syntax highlighting
will become inaccurate.

One way to address this slightly inaccurate syntax highlighting of
function calls, is simply to disable it. An easy way to perform this
is through the use of the =treesit-font-lock-recompute-features=
function. Using this function when loading the major mode will allow
you to customize which features are enabled/disabled from the default
settings.

#+BEGIN_SRC emacs-lisp
(defun ada-ts-mode-setup ()
(treesit-font-lock-recompute-features nil '(function)))

(add-hook 'ada-ts-mode-hook #'ada-ts-mode-setup)
#+END_SRC

If disabling function call highlighting is not sufficiently
satisfying, another approach is to augment the syntax highlighting of
=ada-ts-mode= with that of the Ada language server. The language server
is capable of providing semantic highlighting, which is what is needed
in this situation. Refer to the Ada language server and a
corresponding Emacs Language Server Protocol (LSP) client. Not all
LSP clients for Emacs support semantic highlighting, so investigate
first before selecting one. When semantic highlighting is used with
=ada-ts-mode=, inaccurate function call highlighting will be corrected.
This includes both places where function calls are being highlighted,
which aren't real function calls, as well as places which are function
calls but are not being highlighted. In addition to function call
highlighting, semantic highlighting provides highlighting of other
semantic information, therefore it is highly recommended.

If you do observe places where function call syntax highlighting is
inaccurate and it can be determined from the syntax tree, this is
considered a bug and should be reported by filing an issue against the
package.

* Example Configuration

The following is an example configuration using =use-package= to manage
Expand Down
88 changes: 79 additions & 9 deletions ada-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

;; Author: Troy Brown <[email protected]>
;; Created: February 2023
;; Version: 0.6.0
;; Version: 0.6.1
;; Keywords: ada languages tree-sitter
;; URL: https://github.com/brownts/ada-ts-mode
;; Package-Requires: ((emacs "29.1"))
Expand Down Expand Up @@ -226,6 +226,7 @@ the string property to those instances."
:feature 'attribute
'(((attribute_designator) @font-lock-property-use-face)
(range_attribute_designator "range" @font-lock-property-use-face)
(reduction_attribute_designator (identifier) @font-lock-property-use-face)
(component_declaration (identifier) @font-lock-property-name-face)
(component_choice_list (identifier) @font-lock-property-name-face)
(component_clause local_name: _ @font-lock-property-name-face))
Expand Down Expand Up @@ -308,10 +309,18 @@ the string property to those instances."
(generic_instantiation
["procedure" "function"]
generic_name: [(identifier) (string_literal)] @font-lock-function-name-face)
(generic_instantiation
["procedure" "function"]
generic_name: (function_call name: [(identifier) (string_literal)]
@font-lock-function-name-face))
(generic_instantiation
["procedure" "function"]
generic_name: (selected_component
selector_name: _ @font-lock-function-name-face))
(generic_instantiation
["procedure" "function"]
generic_name: (function_call name: (selected_component
selector_name: _ @font-lock-function-name-face)))
(subprogram_renaming_declaration
callable_entity_name: [(identifier) (string_literal)] @font-lock-function-name-face)
(subprogram_renaming_declaration
Expand Down Expand Up @@ -366,16 +375,54 @@ the string property to those instances."
;; Function/Procedure Calls
:language 'ada
:feature 'function
'((function_call
name: [(identifier) (string_literal)] @font-lock-function-call-face)
(function_call
name: (selected_component
selector_name: _ @font-lock-function-call-face))
:override 'prepend
'(((function_call
name: [(identifier) (string_literal)] @font-lock-function-call-face
:anchor (comment) :*
:anchor (actual_parameter_part))
@function-call
(:pred ada-ts-mode--named-function-call-p @function-call))
((function_call
name: (selected_component
selector_name: _ @font-lock-function-call-face)
:anchor (comment) :*
:anchor (actual_parameter_part))
@function-call
(:pred ada-ts-mode--named-function-call-p @function-call))
(function_call (attribute_designator) @font-lock-function-call-face
:anchor (comment) :*
:anchor (actual_parameter_part))
((procedure_call_statement
name: (identifier) @font-lock-function-call-face :anchor)
@procedure-call
(:pred ada-ts-mode--named-procedure-call-p @procedure-call))
((procedure_call_statement
name: (identifier) @font-lock-function-call-face
:anchor (comment) :*
:anchor (actual_parameter_part))
@procedure-call
(:pred ada-ts-mode--named-procedure-call-p @procedure-call))
((procedure_call_statement
name: (selected_component
selector_name: (identifier) @font-lock-function-call-face)
:anchor)
@procedure-call
(:pred ada-ts-mode--named-procedure-call-p @procedure-call))
((procedure_call_statement
name: (selected_component
selector_name: (identifier) @font-lock-function-call-face)
:anchor (comment) :*
:anchor (actual_parameter_part))
@procedure-call
(:pred ada-ts-mode--named-procedure-call-p @procedure-call))
(procedure_call_statement
name: (identifier) @font-lock-function-call-face)
(attribute_designator) @font-lock-function-call-face :anchor)
(procedure_call_statement
name: (selected_component
selector_name: (identifier) @font-lock-function-call-face)))
(attribute_designator) @font-lock-function-call-face
:anchor (comment) :*
:anchor (actual_parameter_part))
(reduction_attribute_designator
(identifier) @font-lock-function-call-face))

;; Keywords
:language 'ada
Expand Down Expand Up @@ -492,6 +539,29 @@ the string property to those instances."

"Font-lock settings for `ada-ts-mode'.")

(defun ada-ts-mode--named-function-call-p (node)
"Check if NODE is a named function call.
Certain places use a function_call node in the syntax tree, such as a
generic instantiation, because it has similar syntax to a function call,
but it isn't an actual function call."
(let ((node-type (treesit-node-type node))
(parent-node-type (treesit-node-type (treesit-node-parent node))))
(and (string-equal node-type "function_call")
(not (string-equal parent-node-type "generic_instantiation"))
(not (string-equal parent-node-type "assignment_statement"))
(let ((function-name (ada-ts-mode--node-to-name
(treesit-node-child-by-field-name node "name"))))
(not (string-suffix-p ".all" function-name 'ignore-case))))))

(defun ada-ts-mode--named-procedure-call-p (node)
"Check if NODE is a named procedure call."
(let ((node-type (treesit-node-type node)))
(and (string-equal node-type "procedure_call_statement")
(let ((procedure-name (ada-ts-mode--node-to-name
(treesit-node-child-by-field-name node "name"))))
(not (string-suffix-p ".all" procedure-name 'ignore-case))))))

(defun ada-ts-mode--mode-in-p (node)
"Check if mode for NODE is \\='in\\='."
(let ((mode-node
Expand Down
51 changes: 51 additions & 0 deletions doc/ada-ts-mode.texi
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Installation
Troubleshooting
* Org Mode Source Code Blocks::
* Function Calls not Highlighted Correctly::
@end detailmenu
@end menu
Expand Down Expand Up @@ -304,6 +305,7 @@ of the list.

@menu
* Org Mode Source Code Blocks::
* Function Calls not Highlighted Correctly::
@end menu

@node Org Mode Source Code Blocks
Expand All @@ -326,6 +328,55 @@ setting:
(add-to-list 'org-src-lang-modes '("ada" . ada-ts)))
@end lisp

@node Function Calls not Highlighted Correctly
@section Function Calls not Highlighted Correctly

If you observe places in the syntax highlighting where functions calls
are not being properly highlighted, such as an array being highlighted
as a function call, or a parameterless function call not being
highlighted, this is due to ambiguities in the syntax. From a pure
syntax perspective, array accesses look the same as function calls.
Also parameterless function calls look the same as variable accesses.
In places where it can be determined from the syntax (such as a
generic package instantiation) care is taken to avoid highlighting
these places as function calls. In other places, it cannot be known
from the syntax tree alone and that is where the syntax highlighting
will become inaccurate.

One way to address this slightly inaccurate syntax highlighting of
function calls, is simply to disable it. An easy way to perform this
is through the use of the @samp{treesit-font-lock-recompute-features}
function. Using this function when loading the major mode will allow
you to customize which features are enabled/disabled from the default
settings.

@lisp
(defun ada-ts-mode-setup ()
(treesit-font-lock-recompute-features nil '(function)))
(add-hook 'ada-ts-mode-hook #'ada-ts-mode-setup)
@end lisp

If disabling function call highlighting is not sufficiently
satisfying, another approach is to augment the syntax highlighting of
@samp{ada-ts-mode} with that of the Ada language server. The language server
is capable of providing semantic highlighting, which is what is needed
in this situation. Refer to the Ada language server and a
corresponding Emacs Language Server Protocol (LSP) client. Not all
LSP clients for Emacs support semantic highlighting, so investigate
first before selecting one. When semantic highlighting is used with
@samp{ada-ts-mode}, inaccurate function call highlighting will be corrected.
This includes both places where function calls are being highlighted,
which aren't real function calls, as well as places which are function
calls but are not being highlighted. In addition to function call
highlighting, semantic highlighting provides highlighting of other
semantic information, therefore it is highly recommended.

If you do observe places where function call syntax highlighting is
inaccurate and it can be determined from the syntax tree, this is
considered a bug and should be reported by filing an issue against the
package.

@node Example Configuration
@chapter Example Configuration

Expand Down
72 changes: 72 additions & 0 deletions test/resources/font-lock-function_call.ada
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package body Test is

A1 : Address := System'To_Address (16#0000_0000#);
-- ^ nil
-- ^ (font-lock-function-call-face font-lock-property-use-face)

A2 : Address := System.To_Address (16#0000_0000#);
-- ^ nil
-- ^ font-lock-function-call-face

I1 : Integer := Integer'Min (5, 6);
-- ^ nil
-- ^ (font-lock-function-call-face font-lock-property-use-face)

I2 : Integer := Standard.Integer'Min (5, 6);
-- ^ ^ nil
-- ^ (font-lock-function-call-face font-lock-property-use-face)

I3 : Integer := Integer (5);
-- ^ font-lock-function-call-face

I4 : Integer := Standard.Integer (5);
-- ^ nil
-- ^ font-lock-function-call-face

X1 : Integer := Foo.all (5);
-- ^ nil
-- ^ font-lock-keyword-face

X2 : Integer := Foo.ALL (5);
-- ^ nil
-- ^ font-lock-keyword-face

X3 : Integer := XYZ.Foo.all (5);
-- ^ ^ nil
-- ^ font-lock-keyword-face

X4 : Integer := XYZ.Foo.ALL (5);
-- ^ ^ nil
-- ^ font-lock-keyword-face

X5 : Integer := XYZ.Foo.Ball (5);
-- ^ ^ nil
-- ^ font-lock-function-call-face

X6 : Integer := XYZ.Foo.BALL (5);
-- ^ ^ nil
-- ^ font-lock-function-call-face

X7 : Integer := Foo'Reduce ("+", 0);
-- ^ nil
-- ^ (font-lock-function-call-face font-lock-property-use-face)

X8 : Integer := XYZ.Foo'Reduce ("+", 0);
-- ^ ^ nil
-- ^ (font-lock-function-call-face font-lock-property-use-face)

X9 : Integer := [for J in 1 .. 10 => J]'Reduce ("*", 1);
-- ^ (font-lock-function-call-face font-lock-property-use-face)

X10 : Integer := Integer'Mod (5);
-- ^ nil
-- ^ (font-lock-function-call-face font-lock-property-use-face)

X11 : Integer := "+" (1, 2);
-- ^ font-lock-function-call-face

X12 : Integer := XYZ."+" (1, 2);
-- ^ nil
-- ^ font-lock-function-call-face

end Test;
Loading

0 comments on commit a0c001f

Please sign in to comment.