Skip to content

Commit

Permalink
Fix highlighting of package and project names in variable references.
Browse files Browse the repository at this point in the history
There are ambiguities in variable references (such as Foo'Bar) where
it's ambiguous whether this is a reference to attribute Bar in package
Foo of the current project or a reference to a top-level attribute Bar
in project Foo.  Another ambiguity is Foo.X'Bar, as it's unclear if X
is a package in project Foo with attribute Bar or if Foo.X is a child
project and Bar is a top-level attribute of the child project.  This
issue applies to variable references as well the variable attribute
references demonstrated above, since variables can be declared both at
the project level as well as within a package.

These ambiguities can't be resolved by the syntax tree itself and
would require active checking of package declaration names in the file
or project references (with, extends, etc.) or maintaining a list of
known package names.

If we can't correctly identify package names in variable references,
that leaves a highlighting inconsistency where package declaration
names would be highlighted, but variable references wouldn't.  This
would mean that for consistency, all package name highlighting would
need to be removed.

None of these options are ideal.  Package name highlighting gives
visual cues to the reader, indicating if a variable attribute
reference is for a package or a project.  Since it seems useful for
the user to distinguish this information, the option to maintain a
list of known packages is implemented.  This list is a user option
which contains the currently known package names, however if custom
packages are created, this list can easily be updated.
  • Loading branch information
brownts committed Apr 4, 2024
1 parent 45d1858 commit a92ab10
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 6 deletions.
24 changes: 24 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,30 @@ the different font lock levels.
- Level 3 :: attribute, function, number, operator, package, variable
- Level 4 :: bracket, delimiter, error

The following user options can be customized to modify the syntax
highlighting characteristics:

- User Option: gpr-ts-mode-package-names :: List of known package
names to highlight within variable references. There are
ambiguities in the syntax tree for variable references. For
example, ~Foo'Bar~ could be a reference to the ~Bar~ attribute in the
package ~Foo~ of the current project, or it could be a reference to
the top-level ~Bar~ attribute in the project ~Foo~. There are
additional ambiguities when child packages are involved since
references such as ~Foo.X'Bar~ exhibit the same problem. This
ambiguity applies not just to variable attribute references but also
to regular variable references since variables can exist at the
project level as well as within a package. If package names in
variable references are not properly highlighted, this causes an
inconsistency between highlighting of package names in package
declarations and not properly highlighting package names in variable
references. Actively monitoring package names in the current
project as well as all withed projects is considered too heavy
handed and instead we settle for maintaining a list of package
names. Since custom package names can be introduced (although
uncommon), the list of names can be customized, but is initialized
with the set of well-known names.

* Indentation

Indentation follows the nesting structure of the language. Each
Expand Down
28 changes: 27 additions & 1 deletion doc/gpr-ts-mode.texi
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,32 @@ attribute, function, number, operator, package, variable
bracket, delimiter, error
@end table

The following user options can be customized to modify the syntax
highlighting characteristics:

@defopt gpr-ts-mode-package-names
List of known package
names to highlight within variable references. There are
ambiguities in the syntax tree for variable references. For
example, @code{Foo'Bar} could be a reference to the @code{Bar} attribute in the
package @code{Foo} of the current project, or it could be a reference to
the top-level @code{Bar} attribute in the project @code{Foo}. There are
additional ambiguities when child packages are involved since
references such as @code{Foo.X'Bar} exhibit the same problem. This
ambiguity applies not just to variable attribute references but also
to regular variable references since variables can exist at the
project level as well as within a package. If package names in
variable references are not properly highlighted, this causes an
inconsistency between highlighting of package names in package
declarations and not properly highlighting package names in variable
references. Actively monitoring package names in the current
project as well as all withed projects is considered too heavy
handed and instead we settle for maintaining a list of package
names. Since custom package names can be introduced (although
uncommon), the list of names can be customized, but is initialized
with the set of well-known names.
@end defopt

@node Indentation
@chapter Indentation

Expand Down Expand Up @@ -403,4 +429,4 @@ and configuration of recommended supporting packages and modes.

@printindex vr

@bye
@bye
26 changes: 23 additions & 3 deletions gpr-ts-mode.el
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
;;; gpr-ts-mode.el --- Major mode for GNAT project files using Tree-Sitter -*- lexical-binding: t; -*-

;; Copyright (C) 2023 Troy Brown
;; Copyright (C) 2023-2024 Troy Brown

;; Author: Troy Brown <[email protected]>
;; Created: February 2023
;; Version: 0.5.4
;; Version: 0.5.5
;; Keywords: gpr gnat ada languages tree-sitter
;; URL: https://github.com/brownts/gpr-ts-mode
;; Package-Requires: ((emacs "29.1"))
Expand Down Expand Up @@ -111,6 +111,17 @@ specified. See `treesit-language-source-alist' for full details."
:link '(custom-manual :tag "Grammar Installation" "(gpr-ts-mode)Grammar Installation")
:package-version "0.5.0")

(defcustom gpr-ts-mode-package-names
'("binder" "builder" "check" "clean" "compiler" "cross_reference"
"documentation" "eliminate" "finder" "gnatls" "gnatstub"
"ide" "install" "linker" "metrics" "naming" "pretty_printer"
"remote" "stack" "synchronize")
"List of known package names."
:type '(repeat string)
:group 'gpr-ts
:link '(custom-manual :tag "Syntax Highlighting" "(gpr-ts-mode)Syntax Highlighting")
:package-version "0.5.5")

(defvar gpr-ts-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?- ". 12" table)
Expand Down Expand Up @@ -472,7 +483,8 @@ Return nil if no child of that type is found."
'((package_declaration
[ origname: (name (identifier) @font-lock-function-call-face :anchor)
basename: (name (identifier) @font-lock-function-call-face :anchor)])
(variable_reference (name (identifier) @font-lock-function-call-face :anchor) "'"))
((variable_reference (name (identifier) @font-lock-function-call-face))
(:pred gpr-ts-mode--package-name-p @font-lock-function-call-face)))

;; String literals
:language 'gpr
Expand All @@ -487,6 +499,7 @@ Return nil if no child of that type is found."
;; Variables
:language 'gpr
:feature 'variable
:override t
'((variable_reference (name (identifier) @font-lock-variable-use-face :anchor) :anchor))

;; Operators
Expand All @@ -502,6 +515,13 @@ Return nil if no child of that type is found."

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

(defun gpr-ts-mode--package-name-p (node)
"Check if NODE identifier matches a known package name."
(let ((identifier (downcase (treesit-node-text node t)))
(packages (mapcar #'downcase gpr-ts-mode-package-names)))
(seq-find (apply-partially #'string-equal identifier) packages)))


;;; Imenu

(defun gpr-ts-mode--defun-name (node)
Expand Down
12 changes: 10 additions & 2 deletions test/gpr-ts-mode-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
;;; Code:

(require 'ert)
(require 'ert-font-lock nil 'noerror) ; Emacs 30+
(require 'ert-x)
(require 'gpr-ts-mode)
(require 'treesit)
Expand Down Expand Up @@ -102,8 +103,15 @@ otherwise check that there is no error in the parse tree."
((string-prefix-p "filling" file-noext) #'filling-transform)
((string-prefix-p "indent" file-noext) #'indent-transform)
(t #'default-transform))))
(eval `(ert-deftest ,(intern (concat "gpr-ts-mode-test-" file-noext)) ()
(ert-test-erts-file ,file-path #',transform)))))
(if (string-prefix-p "font-lock" file-noext)
(eval `(ert-deftest ,(intern (concat "gpr-ts-mode-" file-noext)) ()
(skip-unless (featurep 'ert-font-lock))
(with-temp-buffer
(insert-file-contents ,file-path)
(funcall #',transform))
(ert-font-lock-test-file ,file-path 'gpr-ts-mode)))
(eval `(ert-deftest ,(intern (concat "gpr-ts-mode-test-" file-noext)) ()
(ert-test-erts-file ,file-path #',transform))))))

(provide 'gpr-ts-mode-tests)

Expand Down
49 changes: 49 additions & 0 deletions test/resources/font-lock-variable_reference.gpr
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
with "shared";

project Test is

for Source_Dirs use Shared'Source_Dirs &
-- <- nil
-- ^ font-lock-property-use-face
Test'Source_Dirs &
-- <- nil
-- ^ font-lock-property-use-face
Project'Source_Dirs;
-- ^ nil
-- ^ font-lock-property-use-face

package Compiler is
for Default_Switches ("Ada") use Compiler'Default_Switches ("Ada") & ("-gnata");
-- ^ font-lock-function-call-face
-- ^ font-lock-property-use-face

for Default_Switches ("Ada") use Shared.Compiler'Default_Switches ("Ada");
-- ^ nil
-- ^ font-lock-function-call-face
-- ^ font-lock-property-use-face

for Default_Switches ("Ada") use Shared.Child.Compiler'Default_Switches ("Ada");
-- ^ ^ nil
-- ^ font-lock-function-call-face
-- ^ font-lock-property-use-face

for Default_Switches ("Ada") use Compiler.My_Switches;
-- ^ font-lock-function-call-face
-- ^ font-lock-variable-use-face

for Default_Switches ("Ada") use Shared.My_Switches;
-- ^ nil
-- ^ font-lock-variable-use-face

for Default_Switches ("Ada") use Shared.Compiler.My_Switches;
-- ^ nil
-- ^ font-lock-function-call-face
-- ^ font-lock-variable-use-face

for Default_Switches ("Ada") use Shared.Child.Compiler.My_Switches;
-- ^ ^ nil
-- ^ font-lock-function-call-face
-- ^ font-lock-variable-use-face
end Compiler;

end Test;

0 comments on commit a92ab10

Please sign in to comment.