branch: externals/matlab-mode
commit f98b9e8434fd7cbb90838e4ecc63842698a49a16
Author: John Ciolfi <[email protected]>
Commit: John Ciolfi <[email protected]>
matlab-ts-mode: initial commit with font-lock support
See comments at top of matlab-ts-mode.el and also see
contributing/treesit-mode-how-to.org
---
Makefile | 16 +-
contributing/treesit-mode-how-to.org | 202 ++++++++++++++
matlab-ts-mode.el | 297 +++++++++++++++++++++
tests/Makefile | 11 +-
tests/metest.el | 20 +-
.../test-matlab-ts-mode-font-lock-files/MyClass.m | 26 ++
.../MyClass_expected.txt | 26 ++
.../MySubSubClass.m | 24 ++
.../MySubSubClass_expected.txt | 24 ++
.../MySubclass.m | 24 ++
.../MySubclass_expected.txt | 24 ++
.../test-matlab-ts-mode-font-lock-files/comments.m | 17 ++
.../comments_expected.txt | 17 ++
.../multiArgFcn.m | 11 +
.../multiArgFcn_expected.txt | 11 +
tests/test-matlab-ts-mode-font-lock-files/my_fcn.m | 26 ++
.../my_fcn_expected.txt | 26 ++
.../small_in_args.m | 7 +
.../small_in_args_expected.txt | 7 +
.../small_no_args.m | 7 +
.../small_no_args_expected.txt | 7 +
.../small_no_help_comment.m | 6 +
.../small_no_help_comment_expected.txt | 6 +
.../small_out_args.m | 7 +
.../small_out_args_expected.txt | 7 +
.../strings_test.m | 9 +
.../strings_test_expected.txt | 9 +
.../test_arguments.m | 9 +
.../test_arguments_expected.txt | 9 +
.../test_classdef_MultiplePropBlocks.m | 10 +
.../test_classdef_MultiplePropBlocks_expected.txt | 10 +
.../test_classdef_PropertyAccess.m | 9 +
.../test_classdef_PropertyAccess_expected.txt | 9 +
.../test_comment_heading.m | 12 +
.../test_comment_heading_expected.txt | 12 +
.../test_continuation.m | 11 +
.../test_continuation_expected.txt | 11 +
.../test_errors.m | 5 +
.../test_errors_expected.txt | 5 +
.../test_missing_continuation.m | 8 +
.../test_missing_continuation_expected.txt | 8 +
tests/test-matlab-ts-mode-font-lock.el | 160 +++++++++++
42 files changed, 1147 insertions(+), 15 deletions(-)
diff --git a/Makefile b/Makefile
index e3278749cc..492dea3c9b 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,14 @@ LOADDEFS = matlab-autoload.el
LOADDIRS = .
EL_SRCS = $(filter-out $(LOADDEFS), $(wildcard *.el))
+
+# Emacs 30 or later has treesit built-in. If running older Emacs, don't build
it.
+HAVE_TREESIT_EMACS = $(shell "$(EMACS)" --batch -Q --eval \
+ "(when (>= emacs-major-version 30) (message
\"have-treesit\"))" 2>&1)
+ifneq ($(filter have-treesit,$(HAVE_TREESIT_EMACS)),have-treesit)
+ EL_SRCS := $(filter-out matlab-ts-mode.el,$(EL_SRCS))
+endif
+
ELC = $(EL_SRCS:.el=.elc)
GOALS := $(if $(MAKECMDGOALS),$(MAKECMDGOALS),all)
@@ -42,7 +50,7 @@ all: lisp tests
.PHONY: lisp
lisp: $(LOADDEFS) $(ELC)
-HAVE_OLD_EMACS = $(shell $(EMACS) --batch -Q --eval \
+HAVE_OLD_EMACS = $(shell "$(EMACS)" --batch -Q --eval \
"(when (<= emacs-major-version 28) (message
\"have-old-emacs\"))" 2>&1)
ifeq ($(filter have-old-emacs,$(HAVE_OLD_EMACS)),have-old-emacs)
BATCH_UPDATE = --eval '(setq generated-autoload-file "$(abspath
$(LOADDEFS))")' \
@@ -52,7 +60,7 @@ else
endif
$(LOADDEFS): | .clean.tstamp
- $(EMACS) $(EMACSFLAGS) $(addprefix -L ,$(LOADPATH)) $(BATCH_UPDATE)
+ "$(EMACS)" $(EMACSFLAGS) $(addprefix -L ,$(LOADPATH)) $(BATCH_UPDATE)
CHECK_FOR_LEXICAL_BINDING = \
@awk 'NR==1 && !/-*- lexical-binding: t -*-/ { \
@@ -60,7 +68,7 @@ CHECK_FOR_LEXICAL_BINDING = \
%.elc: %.el | $(LOADDEFS)
$(CHECK_FOR_LEXICAL_BINDING) $<
- $(EMACS) $(EMACSFLAGS) $(addprefix -L ,$(LOADPATH)) -f
batch-byte-compile $<
+ "$(EMACS)" $(EMACSFLAGS) $(addprefix -L ,$(LOADPATH)) -f
batch-byte-compile $<
$(ELC): $(LOADDEFS) $(MAKEFILE_LIST) | .clean.tstamp
@@ -83,7 +91,7 @@ tests: .tests.tstamp
ALL_FILES = $(wildcard */* */* */*/* */*/*/* */*/*/*/*)
.tests.tstamp: $(LOADDEFS) $(ELC) $(ALL_FILES)
- $(MAKE) -C tests
+ $(MAKE) EMACS="$(EMACS)" -C tests
@touch $@
# When switching emacs versions, we need to clean generated files
diff --git a/contributing/treesit-mode-how-to.org
b/contributing/treesit-mode-how-to.org
new file mode 100644
index 0000000000..5a9ff32477
--- /dev/null
+++ b/contributing/treesit-mode-how-to.org
@@ -0,0 +1,202 @@
+# File: contributing/treesit-mode-how-to.org
+
+# | Copyright 2025 Free Software Foundation, Inc.
+# |
+# | This program is free software: you can redistribute it and/or modify
+# | it under the terms of the GNU General Public License as published by
+# | the Free Software Foundation, either version 3 of the License, or
+# | (at your option) any later version.
+# |
+# | This program is distributed in the hope that it will be useful,
+# | but WITHOUT ANY WARRANTY; without even the implied warranty of
+# | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# | GNU General Public License for more details.
+# |
+# | You should have received a copy of the GNU General Public License
+# | along with this program. If not, see <http://www.gnu.org/licenses/>.
+# |
+# | Commentary:
+# |
+# | Use this as a template for creating org-files with MATLAB and other
language code blocks.
+# | The '#+COMMENT' lines configure org-mode.
+
+#+title: MATLAB and Tree-Sitter
+#+author: John Ciolfi
+#+date: Jun-13-2025
+
+* Overview
+
+This is a set of notes that I'm taking as I develop matlab-ts-mode.el with the
goal of this
+becoming a guide for writting a tree-sitter mode for Emacs 30 or later.
+
+* Issues
+
+- [ ] Building libtree-sitter-matlab.dll from src on Windows produces a DLL
that fails.
+
+ - Install MSYS2
+ - Run MSYS2 bash, then: pacman -S gcc
+ - Install gpg from https://www.gpg4win.org/ and place it on on the path
before MSYS2.
+ - Install matlab tree sitter from src using Emacs 30.1
+ #+begin_example
+ emacs
+ M-x treesit-install-language-grammar
+ Language: matlab
+ There is no recipe for matlab, do you want to build it interactively? (y
or n) y
+ Enter the URL of the Git repository of the language grammar:
https://github.com/acristoffers/tree-sitter-matlab
+ Enter the tag or branch (default: default branch): abi/14
+ Enter the subdirectory in which the parser.c file resides (default: "src"):
+ Enter the C compiler to use (default: auto-detect):
+ Enter the C++ compiler to use (default: auto-detect):
+ Install to (default: ~/.emacs.d/tree-sitter):
+ #+end_example
+
+ The resulting dll is bad. Maybe gcc 13 is not a valid version of gcc.
+
+ Note the build of the dll from
https://github.com/emacs-tree-sitter/tree-sitter-langs is good.
+
+- [ ] M-x treesit-install-language-grammar should specify the tree-sitter ABI
version.
+
+ Emacs 30.1 is ABI 14 from =(treesit-library-abi-version)=, which is behind
the current tree-sitter
+ version, 15.
+
+ Emacs should do something like:
+
+ : tree-sitter generate --abi 13
+ : gcc src/*.c -I./src -o ~/.emacs.d/tree-sitter/libtree-sitter-matlab.EXT
--shared -fPIC -Os
+
+ where EXT = .dll, .so, or .dylib.
+
+* Guide to building a tree-sitter mode
+
+1. Syntax trees and queries. If you are not familar with the concepts behind
tree-sitter, see
+ https://tree-sitter.github.io/tree-sitter. In particular, learn the notion
of queries and try out
+ queries in the playground section of the site on one of the languages
supported by the site. A
+ good understanding of the syntax tree and queires are required to implement
a new tree-sitter
+ major mode. You don't need to understand how to implement a lanugage parser.
+
+2. Documentation
+
+ -
[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Parsing-Program-Source.html][Emacs
manual: Parsing Program Source]]
+ -
[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Parser_002dbased-Indentation.html][Emacs
manual: Parser-based Indentation]]
+
+3. Place the tree-sitter language library in
=~/.emacs.d/tree-sitter/libtree-sitter-LANGUAGE.EXT=
+ (EXT=.so on Linux, .dll on Windows, .dylib on Mac)
+
+ - You can grab the LANGUAGE.EXT from
https://github.com/emacs-tree-sitter/tree-sitter-langs/releases
+ and rename it to =~/.emacs.d/tree-sitter/libtree-sitter-LANGUAGE.EXT=.
+
+ - You can build it using
+
+ : M-x treesit-install-language-grammar
+
+4. Create a basic LANGUAGE-ts-mode.el to validate your tree-sitter shared
library is good.
+
+ It is possible that =~/.emacs.d/tree-sitter/libtree-sitter-LANGUAGE.EXT=
was built incorrectly,
+ so we create the following to validate it, replacing LANGUAGE with your
language name.
+
+ #+begin_src emacs-lisp
+ ;; Basic LANGUAGE-ts-mode.el
+
+ (require 'treesit)
+
+ (define-derived-mode LANGUAGE-ts-mode prog-mode "LANGUAGE"
+ "Major mode for editing LANGUAGE files with tree-sitter."
+
+ (when (treesit-ready-p 'LANGUAGE)
+ (treesit-parser-create 'LANGUAGE)
+ (treesit-major-mode-setup)))
+
+ (provide 'LANGUAGE-ts-mode)
+
+ #+end_src
+
+ Validate your LANGAUGE-ts-mode works. Create foo.txt containing valid
LANGUAGE content, then open
+ foo.txt in Emacs and run:
+
+ : M-x LANGUAGE-ts-mode
+
+ You should now be able to use:
+
+ : M-x treesit-inspect-mode
+ : M-x treesit-explore-mode
+
+5. Setup font-lock
+
+ Queries are needed to identify syntax tree nodes to fontify. See
+
https://www.gnu.org/software/emacs/manual/html_node/elisp/Pattern-Matching.html
+
+ You can use =M-x treesit-explore-mode= to see the nodes of the syntax tree.
+
+ An example of a query that identifies comments (assuming =comment= is a
valid node type), in a
+ file that has =M-x LANGUAGE-ts-mode= active.
+
+ : M-: (treesit-query-capture (treesit-buffer-root-node) '((comment)
@comments))
+
+ Suppose your lanugage contains the keyword "if", you can find all "if"
keywords using:
+
+ : M-: (treesit-query-capture (treesit-buffer-root-node) '("if" @keywords))
+
+ To capture all keywords of your language, use alternation. Here we are
capturing the "if"
+ and "else" keywords:
+
+ : M-: (treesit-query-capture (treesit-buffer-root-node) '(["if" "else"]
@keywords))
+
+ Note, to validate your queries use:
+
+ : M-x (treesit-query-validate 'LANGUAGE '(QUERRY @catpture-name))
+
+ Once we know the queries, we can setup font-lock. For example, here we
fontify comments
+ and keywords.
+
+ #+begin_src emacs-lisp
+ (require 'treesit)
+
+ (defvar LANGUAGE-ts-mode--keywords
+ '("else"
+ "if"
+ ;; <snip>
+ )
+ "LANGUAGE keywords for tree-sitter font-locking.")
+
+ (defvar LANGUAGE-ts-mode--font-lock-settings
+ (treesit-font-lock-rules
+ :language 'LANGUAGE
+ :feature 'comment
+ '((comment) @font-lock-comment-face)
+
+ :language 'LANGUAGE
+ :feature 'keyword
+ `([,@LANGUAGE-ts-mode--keywords] @font-lock-keyword-face)))
+ "LANGUAGE tree-sitter font-lock settings.")
+
+ ;;;###autoload
+ (define-derived-mode LANGUAGE-ts-mode prog-mode "LANGUAGE"
+ "Major mode for editing LANGUAGE files using tree-sitter."
+
+ (when (treesit-ready-p 'LANGUAGE)
+ (treesit-parser-create 'LANGUAGE)
+
+ ;; Font-lock
+ (setq-local treesit-font-lock-settings
LANGUAGE-ts-mode--font-lock-settings)
+
+ ;; `treesit-font-lock-feature-list' contains four sublists where the
first
+ ;; sublist is level 1, and so on. Each sublist contains a set of
feature
+ ;; names that correspond to the
+ ;; :feature 'NAME
+ ;; entries in LANGUAGE-ts-mode--font-lock-settings. For example,
'comment for comments,
+ ;; 'definition for function definitions, 'keyword for language
keywords, etc.
+ ;; Font-lock applies the faces defined in each sublist up to and
including
+ ;; `treesit-font-lock-level', which defaults to 3.
+ (setq-local treesit-font-lock-feature-list
+ '((comment definition)
+ (keyword string type)
+ (builtin constant escape-sequence label number)
+ (bracket delimiter error function operator property
variable)))
+
+ (treesit-major-mode-setup)))
+ #+end_src
+
+ Notice how the @capture-name in the comment query is
@font-lock-comment-face. This face is
+ applied to the items captured by the query. You can see available faces by
using =M-x
+ list-faces-display=. You'll probably want to stick with faces that come
with stock Emacs to
+ avoid dependenices on other packages or create your own face.
diff --git a/matlab-ts-mode.el b/matlab-ts-mode.el
new file mode 100644
index 0000000000..d558886a94
--- /dev/null
+++ b/matlab-ts-mode.el
@@ -0,0 +1,297 @@
+;;; matlab-ts-mode.el --- MATLAB Tree-Sitter Mode -*- lexical-binding: t -*-
+
+;; Copyright 2025 Free Software Foundation, Inc.
+;;
+;; URL: https://github.com/mathworks/Emacs-MATLAB-Mode
+;; SPDX-License-Identifier: GPL-3.0-or-later
+;;
+;; Author: John Ciolfi <[email protected]>
+;; Maintainer: Eric M. Ludlam <[email protected]>, Uwe Brauer
<[email protected]>, John Ciolfi <[email protected]>
+;; Created: Jun-6-2025
+;; Keywords: MATLAB(R)
+;; Package-Requires: ((emacs "30.1"))
+
+;; This file is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation, either version 3 of the License,
+;; or (at your option) any later version.
+;;
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; Tree-sitter, https://tree-sitter.github.io/tree-sitter
+;; based matlab mode: `matlab-ts-mode'
+;; using https://github.com/acristoffers/tree-sitter-matlab
+;;
+;; Install tree-sitter-matlab by taking the matlab.EXT (EXT= .dll, .so, .dylib)
+;; from the latest release of
https://github.com/emacs-tree-sitter/tree-sitter-langs
+;; and rename it to ~/.emacs.d/tree-sitter/libtree-sitter-matlab.EXT
+;;
+
+;;; Code:
+
+(require 'treesit)
+
+(defgroup matlab-ts nil
+ "MATLAB(R) tree-sitter mode."
+ :prefix "matlab-ts-"
+ :group 'languages)
+
+(defface matlab-ts-pragma-face
+ '((t :inherit font-lock-comment-face
+ :bold t))
+ "*Face to use for pragma %# lines.")
+
+(defface matlab-ts-string-delimiter-face
+ '((t :inherit font-lock-string-face
+ :bold t))
+ "*Face to use for \\='single quote\\=' and \"double quote\" string
delimiters.")
+
+(defface matlab-ts-comment-heading-face
+ '((t :inherit font-lock-comment-face
+ :overline t
+ :bold t))
+ "Face for \"%% code section\" headings when NOT in
matlab-sections-minor-mode.")
+
+(defvar matlab-ts-mode--keywords
+ ;; Nodes like "if" are captured by their text because they are part of a
bigger node that captures
+ ;; them as such (and need more than just their text to define the node), but
it doesn't make much
+ ;; sense to create a node for the text "break", "continue", etc. because
that would create two
+ ;; nodes for the same purpose, where one is sufficient. In other words,
"break" like nodes are
+ ;; captured as named nodes, not as unnamed ones, so you need to use their
node names instead of
+ ;; the "content". See
https://github.com/acristoffers/tree-sitter-matlab/issues/25
+ ;;
+ ;; Keywords are documented here
https://www.mathworks.com/help/matlab/ref/iskeyword.html
+ ;; Note, arguments, methods, properties are semi-keywords in that in the
right location
+ ;; the are keywords, otherwise in the wrong location they are variables, but
tree-sitter
+ ;; correctly handles them by letting use look for these as content of the
nodes.
+ '(
+ "arguments"
+ (break_statement)
+ "case"
+ "catch"
+ "classdef"
+ (continue_statement)
+ "else"
+ "elseif"
+ "end"
+ "for"
+ "function"
+ "global"
+ "if"
+ "methods"
+ "otherwise"
+ "parfor"
+ "persistent"
+ "properties"
+ (return_statement)
+ "switch"
+ "try"
+ "while")
+ "MATLAB keywords for tree-sitter font-locking.")
+
+(defvar matlab-ts-mode--type-functions
+ '("double"
+ "single"
+ "int8"
+ "int16"
+ "int32"
+ "int64"
+ "uint8"
+ "uint16"
+ "uint32"
+ "uint64")
+ "MATLAB data type functions.")
+
+(defun matlab-ts-mode--doc-comment-capture (comment-node override start end
&rest _)
+ "Fontify function/classdef documentation comments.
+In MATLAB,
+ function out = myFunction
+ % The documentation help comment for myFunction immediately follows the
+ % function defintion.
+
+ % code comment have a blank line
+ out = 1;
+ end
+
+ function out = myFunctionWithoutHelp
+
+ % code comment have a blank line
+ out = 1;
+ end
+
+COMMENT-NODE is the tree-sitter node from the \"doc comments\"
+treesit-font-lock-rules rule and OVERRIDE is from that rule.
+START and END specify the region to be fontified."
+ (let* ((prev-node (treesit-node-prev-sibling comment-node))
+ (prev-node-type (treesit-node-type prev-node))
+ (prev2-node (when (string= prev-node-type "identifier")
+ (treesit-node-prev-sibling prev-node)))
+ (real-prev-node-type (if prev2-node (treesit-node-type prev2-node)
prev-node-type))
+ (real-prev-node (or prev2-node prev-node))
+ (is-doc-comment-candidate
+ (or (string= real-prev-node-type "function_arguments") ;; function
foo(in)
+ (string= real-prev-node-type "function_output") ;; function
out = foo
+ (string= real-prev-node-type "function") ;; function
foo
+ (string= real-prev-node-type "classdef") ;; classdef
foo
+ (string= real-prev-node-type "superclasses"))) ;; classdef
foo < ParentClass
+ (is-doc-comment (and is-doc-comment-candidate
+ (save-excursion
+ (goto-char (treesit-node-start comment-node))
+ (not (re-search-backward "^[ \t]*$"
+ (treesit-node-end
real-prev-node)
+ t))))))
+ (when is-doc-comment
+ (treesit-fontify-with-override
+ (treesit-node-start comment-node) (treesit-node-end comment-node)
+ font-lock-doc-face override start end))))
+
+(defvar matlab-ts-mode--font-lock-settings
+ (treesit-font-lock-rules
+
+ ;; Comments and line continuation: ... optional text
+ :language 'matlab
+ :feature 'comment
+ '((comment) @font-lock-comment-face
+ (line_continuation) @font-lock-comment-face)
+
+ ;; %#NAME pragma comments
+ :language 'matlab
+ :feature 'comment
+ :override t
+ '(((comment) @matlab-ts-pragma-face (:match "^%#.+$"
@matlab-ts-pragma-face)))
+
+ ;; %% comment heading
+ :language 'matlab
+ :feature 'comment
+ :override t
+ '(((comment) @matlab-ts-comment-heading-face (:match "^%%\\(?:[ \t].+\\)?$"
@matlab-ts-comment-heading-face)))
+
+ ;; Doc help comments
+ :language 'matlab
+ :feature 'comment
+ :override t
+ '((function_definition (comment) @matlab-ts-mode--doc-comment-capture)
+ (class_definition (comment) @matlab-ts-mode--doc-comment-capture))
+
+ ;; Keywords: if, else, etc.
+ :language 'matlab
+ :feature 'keyword
+ `(([,@matlab-ts-mode--keywords] @font-lock-keyword-face)
+ ;; spmd is "incorrectly" identified as an identifier, hence special case
that
+ (((identifier) @font-lock-keyword-face (:equal "spmd"
@font-lock-keyword-face))))
+
+ ;; function/classdef
+ :language 'matlab
+ :feature 'definition
+ '((function_definition name: (identifier) @font-lock-function-name-face)
+ (class_definition name: (identifier) @font-lock-function-name-face)
+ (superclasses (property_name (identifier)) @font-lock-function-name-face))
+
+ ;; Function input and output arguments - variables
+ :language 'matlab
+ :feature 'definition
+ '(
+ ;; The input arguments: function functionName(in1, in2, in3)
+ (function_arguments arguments:
+ (identifier) @font-lock-variable-name-face
+ ("," (identifier) @font-lock-variable-name-face) :*)
+ ;; Single output arugment: function out = functionName(in1, in2)
+ (function_output (identifier) @font-lock-variable-name-face)
+ ;; Multiple ouptut arguments: function [out1, out2] = functionName(in1,
in2)
+ (function_output (multioutput_variable (identifier)
@font-lock-variable-name-face))
+ ;; arguments block
+ (arguments_statement (property name:
+ (identifier) @font-lock-variable-name-face
+ (_) @font-lock-type-face))
+ (arguments_statement (property name:
+ (property_name)
@font-lock-variable-name-face
+ (_) @font-lock-type-face)))
+
+ ;; classdef MyClass
+ ;; properties (attributes)
+ ;; Properties
+ ;; end
+ :language 'matlab
+ :feature 'definition
+ '(
+ ;; Property block attributes
+ (attribute (identifier) @font-lock-type-face "=" (_)
@font-lock-builtin-face)
+ (attribute (identifier) @font-lock-type-face)
+ (property name: (identifier) @font-lock-variable-name-face))
+
+ ;; Strings
+ :language 'matlab
+ :feature 'string
+ '((string_content) @font-lock-string-face
+ ((string_content) ["\"" "'"]) @matlab-ts-string-delimiter-face
+ (string ["\"" "'"] @matlab-ts-string-delimiter-face))
+
+ ;; Transpose uses "'" after an identifier, e.g. for matrix A we tranpose it
via: A' since "'" is
+ ;; also used as a string, we use a different face for transpose and put it
under the string
+ ;; category.
+ :language 'matlab
+ :feature 'string
+ '((postfix_operator "'" @font-lock-function-name-face))
+
+ ;; Types, e.g. int32()
+ :language 'matlab
+ :feature 'type
+ `((function_call name: (identifier)
+ @font-lock-type-face
+ (:match ,(rx-to-string
+ `(seq bol
+ (or ,@matlab-ts-mode--type-functions)
+ eol))
+ @font-lock-type-face)))
+
+ ;; Constant numbers
+ :language 'matlab
+ :feature 'number
+ '((number) @font-lock-constant-face)
+
+ ;; Brackets
+ :language 'matlab
+ :feature 'bracket
+ '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
+
+ ;; Delimiters
+ :language 'matlab
+ :feature 'delimiter
+ '((["," "." ";" ":" "@" "?"]) @font-lock-delimiter-face)
+
+ ;; Syntax errors
+ :language 'matlab
+ :feature 'error
+ :override t
+ '((ERROR) @font-lock-warning-face)
+
+ )
+ "MATLAB tree-sitter font-lock settings.")
+
+;;;###autoload
+(define-derived-mode matlab-ts-mode prog-mode "MATLAB:ts"
+ "Major mode for editing MATLAB files with tree-sitter."
+
+ (when (treesit-ready-p 'matlab)
+ (treesit-parser-create 'matlab)
+
+ ;; Font-lock
+ (setq-local treesit-font-lock-settings matlab-ts-mode--font-lock-settings)
+ (setq-local treesit-font-lock-feature-list
+ '((comment definition)
+ (keyword string type variable)
+ (number)
+ (bracket delimiter error)))
+
+ (treesit-major-mode-setup)))
+
+(provide 'matlab-ts-mode)
+;;; matlab-ts-mode.el ends here
diff --git a/tests/Makefile b/tests/Makefile
index 0d1d2885ef..d51a65516f 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -14,7 +14,7 @@
# along with this program. If not, see http://www.gnu.org/licenses/.
#
-EMACS = emacs
+EMACS ?= emacs
EMACSFLAGS = --batch -Q --eval '(setq debug-on-error t)'
MATLAB_FILES = $(wildcard *.m)
@@ -32,11 +32,16 @@ all: $(TEST_TARGETS)
.PHONY: modetests
modetests: metest.el
- $(EMACS) $(EMACSFLAGS) -l metest.el -e "metest-all-syntax-tests"
+ "$(EMACS)" $(EMACSFLAGS) -l metest.el -e "metest-all-syntax-tests"
.PHONY: shelltests
shelltests: mstest.el
- $(CLEAN_MATLABPATH) $(EMACS) $(EMACSFLAGS) -l mstest.el
$(MATLAB_PROG_SETUP) -e "mstest-run-all-tests"
+ $(CLEAN_MATLABPATH) "$(EMACS)" $(EMACSFLAGS) -l mstest.el
$(MATLAB_PROG_SETUP) -e "mstest-run-all-tests"
+
+.PHONY: test-matlab-ts-mode-font-lock
+test-matlab-ts-mode-font-lock:
+ "$(EMACS)" $(EMACSFLAGS) -l [email protected] -e $@
+
# [eof] tests/Makefile
diff --git a/tests/metest.el b/tests/metest.el
index 87e223b840..7707c4da9a 100644
--- a/tests/metest.el
+++ b/tests/metest.el
@@ -21,15 +21,14 @@
;;; Code:
+(defvar met-testfile-path nil
+ "Location of test MATLAB code.")
+
(let* ((lf (or load-file-name (buffer-file-name (current-buffer))))
(d1 (file-name-directory lf))
- (d (file-name-directory (directory-file-name d1)))
- )
- (defvar met-testfile-path d1
- "Location of test MATLAB code.")
- (add-to-list 'load-path (expand-file-name d) t))
-
-(defvar met-testfile-path) ; quiet compiler
+ (parent-dir (expand-file-name (file-name-directory (directory-file-name
d1)))))
+ (setq met-testfile-path d1)
+ (add-to-list 'load-path parent-dir t))
(require 'matlab-mode)
(require 'mlint)
@@ -41,6 +40,8 @@
(require 'metest-imenu)
(require 'metest-imenu-tlc)
+(require 'test-matlab-ts-mode-font-lock)
+
(defun metest-all-syntax-tests ()
"Run all the syntax test cases in this file."
(setq debug-on-error t)
@@ -81,7 +82,10 @@
;; TODO - enable this test on Windows. It currently fails, so disabling on
;; windows. See https://github.com/mathworks/Emacs-MATLAB-Mode/issues/34
(when (not (eq system-type 'windows-nt))
- (metest-fill-paragraph)))
+ (metest-fill-paragraph))
+
+ ;; matlab-ts-mode tests
+ (metest-run 'test-matlab-ts-mode-font-lock))
(defun metest-run (test)
"Run and time TEST."
diff --git a/tests/test-matlab-ts-mode-font-lock-files/MyClass.m
b/tests/test-matlab-ts-mode-font-lock-files/MyClass.m
new file mode 100644
index 0000000000..920d910ea9
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/MyClass.m
@@ -0,0 +1,26 @@
+% -*- matlab-ts -*-
+classdef MyClass
+% help comment
+
+ % comment about properities
+ properties
+ MyProperty double = 0; % Public property with a default value
+ end
+
+ methods
+ function obj = MyClass(initialValue)
+ % Constructor method
+ obj.MyProperty = initialValue;
+ end
+
+ function newValue = getMyProperty(obj)
+ % Getter method
+ newValue = obj.MyProperty;
+ end
+
+ function obj = setMyProperty(obj, newValue)
+ % Setter method
+ obj.MyProperty = newValue;
+ end
+ end
+end
diff --git a/tests/test-matlab-ts-mode-font-lock-files/MyClass_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/MyClass_expected.txt
new file mode 100644
index 0000000000..430286050f
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/MyClass_expected.txt
@@ -0,0 +1,26 @@
+c ccc ccccccccc ccc
+kkkkkkkk fffffff
+h hhhh hhhhhhh
+
+ c ccccccc ccccc ccccccccccc
+ kkkkkkkkkk
+ vvvvvvvvvv dddddd d nD c cccccc cccccccc cccc c ccccccc ccccc
+ kkk
+
+ kkkkkkk
+ kkkkkkkk vvv d fffffffbvvvvvvvvvvvvb
+ h hhhhhhhhhhh hhhhhh
+ dddDdddddddddd d ddddddddddddD
+ kkk
+
+ kkkkkkkk vvvvvvvv d fffffffffffffbvvvb
+ h hhhhhh hhhhhh
+ dddddddd d dddDddddddddddD
+ kkk
+
+ kkkkkkkk vvv d fffffffffffffbvvvD vvvvvvvvb
+ h hhhhhh hhhhhh
+ dddDdddddddddd d ddddddddD
+ kkk
+ kkk
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/MySubSubClass.m
b/tests/test-matlab-ts-mode-font-lock-files/MySubSubClass.m
new file mode 100644
index 0000000000..fce0d47244
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/MySubSubClass.m
@@ -0,0 +1,24 @@
+% -*- matlab-ts -*-
+classdef MySubClass < ParentClass1 & ParentClass2
+% help comment
+ properties
+ MyProperty double = 0; % Public property with a default value
+ end
+
+ methods
+ function obj = MyClass(initialValue)
+ % Constructor method
+ obj.MyProperty = initialValue;
+ end
+
+ function newValue = getMyProperty(obj)
+ % Getter method
+ newValue = obj.MyProperty;
+ end
+
+ function obj = setMyProperty(obj, newValue)
+ % Setter method
+ obj.MyProperty = newValue;
+ end
+ end
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/MySubSubClass_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/MySubSubClass_expected.txt
new file mode 100644
index 0000000000..5632a069c7
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/MySubSubClass_expected.txt
@@ -0,0 +1,24 @@
+c ccc ccccccccc ccc
+kkkkkkkk ffffffffff d ffffffffffff d ffffffffffff
+h hhhh hhhhhhh
+ kkkkkkkkkk
+ vvvvvvvvvv dddddd d nD c cccccc cccccccc cccc c ccccccc ccccc
+ kkk
+
+ kkkkkkk
+ kkkkkkkk vvv d fffffffbvvvvvvvvvvvvb
+ h hhhhhhhhhhh hhhhhh
+ dddDdddddddddd d ddddddddddddD
+ kkk
+
+ kkkkkkkk vvvvvvvv d fffffffffffffbvvvb
+ h hhhhhh hhhhhh
+ dddddddd d dddDddddddddddD
+ kkk
+
+ kkkkkkkk vvv d fffffffffffffbvvvD vvvvvvvvb
+ h hhhhhh hhhhhh
+ dddDdddddddddd d ddddddddD
+ kkk
+ kkk
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/MySubclass.m
b/tests/test-matlab-ts-mode-font-lock-files/MySubclass.m
new file mode 100644
index 0000000000..6ec1cc08bb
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/MySubclass.m
@@ -0,0 +1,24 @@
+% -*- matlab-ts -*-
+classdef MySubclass < ParentClass
+% help comment
+ properties
+ MyProperty double = 0; % Public property with a default value
+ end
+
+ methods
+ function obj = MyClass(initialValue)
+ % Constructor method
+ obj.MyProperty = initialValue;
+ end
+
+ function newValue = getMyProperty(obj)
+ % Getter method
+ newValue = obj.MyProperty;
+ end
+
+ function obj = setMyProperty(obj, newValue)
+ % Setter method
+ obj.MyProperty = newValue;
+ end
+ end
+end
diff --git a/tests/test-matlab-ts-mode-font-lock-files/MySubclass_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/MySubclass_expected.txt
new file mode 100644
index 0000000000..b860c8d50a
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/MySubclass_expected.txt
@@ -0,0 +1,24 @@
+c ccc ccccccccc ccc
+kkkkkkkk ffffffffff d fffffffffff
+h hhhh hhhhhhh
+ kkkkkkkkkk
+ vvvvvvvvvv dddddd d nD c cccccc cccccccc cccc c ccccccc ccccc
+ kkk
+
+ kkkkkkk
+ kkkkkkkk vvv d fffffffbvvvvvvvvvvvvb
+ h hhhhhhhhhhh hhhhhh
+ dddDdddddddddd d ddddddddddddD
+ kkk
+
+ kkkkkkkk vvvvvvvv d fffffffffffffbvvvb
+ h hhhhhh hhhhhh
+ dddddddd d dddDddddddddddD
+ kkk
+
+ kkkkkkkk vvv d fffffffffffffbvvvD vvvvvvvvb
+ h hhhhhh hhhhhh
+ dddDdddddddddd d ddddddddD
+ kkk
+ kkk
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/comments.m
b/tests/test-matlab-ts-mode-font-lock-files/comments.m
new file mode 100644
index 0000000000..cf60c5f5f5
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/comments.m
@@ -0,0 +1,17 @@
+% -*- mode: matlab-ts -*-
+
+% Single line comment
+
+%{
+ multiline
+ comment
+%}
+
+% Note, "%{" followed by text is a single line comment, e.g. in following,
+% myVar has value 1 and the statement is the same as: myVar = 1
+
+myVar = 1 %{single line comment %} + 1;
+
+% This is also a single line comment:
+
+%{ single line comment
diff --git a/tests/test-matlab-ts-mode-font-lock-files/comments_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/comments_expected.txt
new file mode 100644
index 0000000000..27692f5121
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/comments_expected.txt
@@ -0,0 +1,17 @@
+c ccc ccccc ccccccccc ccc
+
+c cccccc cccc ccccccc
+
+cc
+ ccccccccc
+ ccccccc
+cc
+
+c ccccc cccc cccccccc cc cccc cc c cccccc cccc cccccccc cccc cc cccccccccc
+c ccccc ccc ccccc c ccc ccc ccccccccc cc ccc cccc ccc ccccc c c
+
+wwwww w w wwwwwwww wwww wwwwwww ww w ww
+
+w wwww ww wwww w wwwwww wwww wwwwwwww
+
+ww wwwwww wwww wwwwwww
diff --git a/tests/test-matlab-ts-mode-font-lock-files/multiArgFcn.m
b/tests/test-matlab-ts-mode-font-lock-files/multiArgFcn.m
new file mode 100644
index 0000000000..295b40afdf
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/multiArgFcn.m
@@ -0,0 +1,11 @@
+% -*- mode: matlab-ts -*-
+function [ ...
+ out1 ... % comment for out1
+ out2 ... % comment for out2
+ ] = ...
+ multiArgFcn(in1, ... % comment for in1
+ in2 ... % comment for in2
+ )
+ out1 = in1;
+ out2 = in2;
+end
diff --git a/tests/test-matlab-ts-mode-font-lock-files/multiArgFcn_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/multiArgFcn_expected.txt
new file mode 100644
index 0000000000..2140b687e7
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/multiArgFcn_expected.txt
@@ -0,0 +1,11 @@
+c ccc ccccc ccccccccc ccc
+kkkkkkkk b ccc
+ vvvv ccc c ccccccc ccc cccc
+ vvvv ccc c ccccccc ccc cccc
+ b d ccc
+ fffffffffffbvvvD ccc c ccccccc ccc ccc
+ vvv ccc c ccccccc ccc ccc
+ b
+ dddd d dddD
+ dddd d dddD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/my_fcn.m
b/tests/test-matlab-ts-mode-font-lock-files/my_fcn.m
new file mode 100644
index 0000000000..07eb8b0390
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/my_fcn.m
@@ -0,0 +1,26 @@
+% -*- mode: matlab-ts -*-
+function [out1, out2, out3] = my_fcn(in1, in2, in3)
+% help comment
+% help comment line 2
+
+ % code comment
+ if in1 > double(5) %#some-pragma
+ out1 = sin(10) + in * [2];
+ else
+ out1 = in1 * 3 + 2;
+ end
+
+ qStr = "asdf asdf' asdf '";
+ out2 = foo2(qStr);
+
+ sStr = 'asdf''asdf"';
+ out3 = foo3(sStr);
+end
+
+function out1 = foo2(in1)
+ out1 = in1;
+end
+
+function [out1] = foo3(in1)
+ out1 = in1;
+end
diff --git a/tests/test-matlab-ts-mode-font-lock-files/my_fcn_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/my_fcn_expected.txt
new file mode 100644
index 0000000000..df8800d53e
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/my_fcn_expected.txt
@@ -0,0 +1,26 @@
+c ccc ccccc ccccccccc ccc
+kkkkkkkk bvvvvD vvvvD vvvvb d ffffffbvvvD vvvD vvvb
+h hhhh hhhhhhh
+h hhhh hhhhhhh hhhh h
+
+ c cccc ccccccc
+ kk ddd d ttttttbnb ppppppppppppp
+ dddd d dddbnnb d dd d bnbD
+ kkkk
+ dddd d ddd d n d nD
+ kkk
+
+ dddd d Sssss sssss ssss sSD
+ dddd d ddddbddddbD
+
+ dddd d SsssssssssssSD
+ dddd d ddddbddddbD
+kkk
+
+kkkkkkkk vvvv d ffffbvvvb
+ dddd d dddD
+kkk
+
+kkkkkkkk bvvvvb d ffffbvvvb
+ dddd d dddD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/small_in_args.m
b/tests/test-matlab-ts-mode-font-lock-files/small_in_args.m
new file mode 100644
index 0000000000..4fa8490ec3
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_in_args.m
@@ -0,0 +1,7 @@
+% -*- mode: matlab-ts -*-
+function small_in_args(in)
+% help comment
+
+ % code comment
+ out = in;
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/small_in_args_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/small_in_args_expected.txt
new file mode 100644
index 0000000000..5735ea55b6
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_in_args_expected.txt
@@ -0,0 +1,7 @@
+c ccc ccccc ccccccccc ccc
+kkkkkkkk fffffffffffffbvvb
+h hhhh hhhhhhh
+
+ c cccc ccccccc
+ ddd d ddD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/small_no_args.m
b/tests/test-matlab-ts-mode-font-lock-files/small_no_args.m
new file mode 100644
index 0000000000..208e269f5c
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_no_args.m
@@ -0,0 +1,7 @@
+% -*- mode: matlab-ts -*-
+function small_no_args
+% help comment
+
+ % code comment
+ disp('no args');
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/small_no_args_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/small_no_args_expected.txt
new file mode 100644
index 0000000000..6dd6872848
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_no_args_expected.txt
@@ -0,0 +1,7 @@
+c ccc ccccc ccccccccc ccc
+kkkkkkkk fffffffffffff
+h hhhh hhhhhhh
+
+ c cccc ccccccc
+ ddddbSss ssssSbD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/small_no_help_comment.m
b/tests/test-matlab-ts-mode-font-lock-files/small_no_help_comment.m
new file mode 100644
index 0000000000..22757cd157
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_no_help_comment.m
@@ -0,0 +1,6 @@
+% -*- mode: matlab-ts -*-
+function out = small_no_help_comment(in)
+
+ % code comment
+ out = in;
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/small_no_help_comment_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/small_no_help_comment_expected.txt
new file mode 100644
index 0000000000..b152cc0a2b
--- /dev/null
+++
b/tests/test-matlab-ts-mode-font-lock-files/small_no_help_comment_expected.txt
@@ -0,0 +1,6 @@
+c ccc ccccc ccccccccc ccc
+kkkkkkkk vvv d fffffffffffffffffffffbvvb
+
+ c cccc ccccccc
+ ddd d ddD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/small_out_args.m
b/tests/test-matlab-ts-mode-font-lock-files/small_out_args.m
new file mode 100644
index 0000000000..a2c74c3058
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_out_args.m
@@ -0,0 +1,7 @@
+% -*- mode: matlab-ts -*-
+function out = small_out_args()
+% help comment
+
+ % code comment
+ out = 1;
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/small_out_args_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/small_out_args_expected.txt
new file mode 100644
index 0000000000..b7823370ac
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/small_out_args_expected.txt
@@ -0,0 +1,7 @@
+c ccc ccccc ccccccccc ccc
+kkkkkkkk vvv d ffffffffffffffbb
+h hhhh hhhhhhh
+
+ c cccc ccccccc
+ ddd d nD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/strings_test.m
b/tests/test-matlab-ts-mode-font-lock-files/strings_test.m
new file mode 100644
index 0000000000..b3ce31acb1
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/strings_test.m
@@ -0,0 +1,9 @@
+% -*- matlab-ts -*-
+
+s1 = "abc";
+s2 = 'def';
+
+A = [1 2; 3 4];
+B = A'; % transpose
+C = A''; % transpose transpose ==> isequal(C,A)
+D = A'''; % transpose transpose transpose ==> isequal(D,B)
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/strings_test_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/strings_test_expected.txt
new file mode 100644
index 0000000000..985fb49bf8
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/strings_test_expected.txt
@@ -0,0 +1,9 @@
+c ccc ccccccccc ccc
+
+dd d SsssSD
+dd d SsssSD
+
+d d bn nD n nbD
+d d dfD c ccccccccc
+d d dffD c ccccccccc ccccccccc ccc cccccccccccc
+d d dfffD c ccccccccc ccccccccc ccccccccc ccc cccccccccccc
diff --git a/tests/test-matlab-ts-mode-font-lock-files/test_arguments.m
b/tests/test-matlab-ts-mode-font-lock-files/test_arguments.m
new file mode 100644
index 0000000000..cf94ebcdbf
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_arguments.m
@@ -0,0 +1,9 @@
+% -*- matlab-ts -*-
+function test_arguments(in1, properties, options)
+ arguments
+ in1 (1,:) {mustBeNumeric}
+ properties double
+ options.LineStyle (1,1) string = "-"
+ options.LineWidth (1,1) {mustBeNumeric} = 1
+ end
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_arguments_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_arguments_expected.txt
new file mode 100644
index 0000000000..fc26c3d61f
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_arguments_expected.txt
@@ -0,0 +1,9 @@
+c ccc ccccccccc ccc
+kkkkkkkk ffffffffffffffbvvvD vvvvvvvvvvD vvvvvvvb
+ kkkkkkkkk
+ vvv ttttt ttttttttttttttt
+ vvvvvvvvvv tttttt
+ vvvvvvvvvvvvvvvvv ttttt tttttt t ttt
+ vvvvvvvvvvvvvvvvv ttttt ttttttttttttttt t t
+ kkk
+kkk
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_classdef_MultiplePropBlocks.m
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_MultiplePropBlocks.m
new file mode 100644
index 0000000000..eeabc5caa8
--- /dev/null
+++
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_MultiplePropBlocks.m
@@ -0,0 +1,10 @@
+% -*- matlab-ts -*-
+classdef MultiplePropBlocks
+ properties (SetAccess = private, GetAccess = {?OtherClass})
+ Property1
+ Property2
+ end
+ properties (Abstract)
+ Property3
+ end
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_classdef_MultiplePropBlocks_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_MultiplePropBlocks_expected.txt
new file mode 100644
index 0000000000..7fdad344de
--- /dev/null
+++
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_MultiplePropBlocks_expected.txt
@@ -0,0 +1,10 @@
+c ccc ccccccccc ccc
+kkkkkkkk ffffffffffffffffff
+ kkkkkkkkkk bttttttttt d BBBBBBBD ttttttttt d BBBBBBBBBBBBBb
+ vvvvvvvvv
+ vvvvvvvvv
+ kkk
+ kkkkkkkkkk bttttttttb
+ vvvvvvvvv
+ kkk
+kkk
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_classdef_PropertyAccess.m
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_PropertyAccess.m
new file mode 100644
index 0000000000..0b7d5af6ea
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_PropertyAccess.m
@@ -0,0 +1,9 @@
+% -*- matlab-ts -*-
+classdef test_classdef_PropertyAccess
+ properties (GetAccess = {?ClassA, ?ClassB})
+ Prop1
+ end
+ properties (Access = ?ClassC)
+ Prop2
+ end
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_classdef_PropertyAccess_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_PropertyAccess_expected.txt
new file mode 100644
index 0000000000..94cfae4fb2
--- /dev/null
+++
b/tests/test-matlab-ts-mode-font-lock-files/test_classdef_PropertyAccess_expected.txt
@@ -0,0 +1,9 @@
+c ccc ccccccccc ccc
+kkkkkkkk ffffffffffffffffffffffffffff
+ kkkkkkkkkk bttttttttt d BBBBBBBBB BBBBBBBBb
+ vvvvv
+ kkk
+ kkkkkkkkkk btttttt d BBBBBBBb
+ vvvvv
+ kkk
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/test_comment_heading.m
b/tests/test-matlab-ts-mode-font-lock-files/test_comment_heading.m
new file mode 100644
index 0000000000..be83dd4b81
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_comment_heading.m
@@ -0,0 +1,12 @@
+% -*- matlab-ts -*-
+function [a, b] = test_comment_heading
+% help for test_comment_heading
+
+ %% comment heading 1
+
+ a = 1;
+
+ %% comment heading 2
+
+ b = 1;
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_comment_heading_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_comment_heading_expected.txt
new file mode 100644
index 0000000000..94af062170
--- /dev/null
+++
b/tests/test-matlab-ts-mode-font-lock-files/test_comment_heading_expected.txt
@@ -0,0 +1,12 @@
+c ccc ccccccccc ccc
+kkkkkkkk bvD vb d ffffffffffffffffffff
+h hhhh hhh hhhhhhhhhhhhhhhhhhhh
+
+ HH HHHHHHH HHHHHHH H
+
+ d d nD
+
+ HH HHHHHHH HHHHHHH H
+
+ d d nD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/test_continuation.m
b/tests/test-matlab-ts-mode-font-lock-files/test_continuation.m
new file mode 100644
index 0000000000..bb2ef953a9
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_continuation.m
@@ -0,0 +1,11 @@
+% -*- matlab-ts -*-
+function [ ...
+ out1, ... comment for out1
+ out2 ... comment for out2
+ ] = test_continuation( ...
+ in1, ... comment for in1
+ in2 ... comment for in2
+ )
+ out1 = in1;
+ out2 = in2;
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_continuation_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_continuation_expected.txt
new file mode 100644
index 0000000000..924fd69a06
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_continuation_expected.txt
@@ -0,0 +1,11 @@
+c ccc ccccccccc ccc
+kkkkkkkk b ccc
+ vvvvD ccc ccccccc ccc cccc
+ vvvv ccc ccccccc ccc cccc
+ b d fffffffffffffffffb ccc
+ vvvD ccc ccccccc ccc ccc
+ vvv ccc ccccccc ccc ccc
+ b
+ dddd d dddD
+ dddd d dddD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock-files/test_errors.m
b/tests/test-matlab-ts-mode-font-lock-files/test_errors.m
new file mode 100644
index 0000000000..ac40500b01
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_errors.m
@@ -0,0 +1,5 @@
+% -*- matlab-ts -*-
+function test_errors
+ a = 1;
+ b =** 2;
+end
diff --git a/tests/test-matlab-ts-mode-font-lock-files/test_errors_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_errors_expected.txt
new file mode 100644
index 0000000000..61bee736a0
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_errors_expected.txt
@@ -0,0 +1,5 @@
+c ccc ccccccccc ccc
+kkkkkkkk fffffffffff
+ d d nD
+ d wwd nD
+kkk
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_missing_continuation.m
b/tests/test-matlab-ts-mode-font-lock-files/test_missing_continuation.m
new file mode 100644
index 0000000000..4e812eae1b
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock-files/test_missing_continuation.m
@@ -0,0 +1,8 @@
+% -*- matlab-ts -*-
+% Missing continuation should result in (ERROR) nodes
+% Continuation "..." is needed after the "(" and after the "in1".
+function test_missing_continuation(
+ in
+ )
+ disp(num2str(in));
+end
diff --git
a/tests/test-matlab-ts-mode-font-lock-files/test_missing_continuation_expected.txt
b/tests/test-matlab-ts-mode-font-lock-files/test_missing_continuation_expected.txt
new file mode 100644
index 0000000000..bd5d47f21c
--- /dev/null
+++
b/tests/test-matlab-ts-mode-font-lock-files/test_missing_continuation_expected.txt
@@ -0,0 +1,8 @@
+c ccc ccccccccc ccc
+c ccccccc cccccccccccc cccccc cccccc cc ccccccc ccccc
+c cccccccccccc ccccc cc cccccc ccccc ccc ccc ccc ccccc ccc cccccc
+kkkkkkkk fffffffffffffffffffffffffb
+ vv
+ b
+ ddddbdddddddbddbbD
+kkk
diff --git a/tests/test-matlab-ts-mode-font-lock.el
b/tests/test-matlab-ts-mode-font-lock.el
new file mode 100644
index 0000000000..340b2cf1e1
--- /dev/null
+++ b/tests/test-matlab-ts-mode-font-lock.el
@@ -0,0 +1,160 @@
+;;; test-matlab-ts-mode-font-lock.el --- Testing suite for MATLAB Emacs -*-
lexical-binding: t -*-
+;;
+;; Copyright Free Software Foundation
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see http://www.gnu.org/licenses/.
+
+;;; Commentary:
+;;
+;; Validate font-lock faces in matlab-ts-mode
+
+;;; Code:
+
+(let* ((lf (or load-file-name (buffer-file-name (current-buffer))))
+ (d1 (file-name-directory lf))
+ (parent-dir (expand-file-name (file-name-directory (directory-file-name
d1)))))
+ (add-to-list 'load-path parent-dir t))
+
+(require 'cl-macs)
+(require 'matlab-ts-mode)
+
+(defun test-matlab-ts-mode-font-lock-files ()
+ "Return list of full paths to each test-matlab-ts-mode-font-lock-files/*.m."
+ (directory-files "test-matlab-ts-mode-font-lock-files" t "\\.m$"))
+
+(defvar test-matlab-ts-mode-font-lock
+ (cons "test-matlab-ts-mode-font-lock" (test-matlab-ts-mode-font-lock-files)))
+
+(cl-defun test-matlab-ts-mode-font-lock (&optional m-file)
+ "Test font-lock using ./test-matlab-ts-mode-font-lock-files/M-FILE.
+Compare ./test-matlab-ts-mode-font-lock-files/M-FILE against
+./test-matlab-ts-mode-font-lock-files/NAME_expected.txt, where
+NAME_expected.txt is of same length as M-FILE and has a character for
+each face setup by font-lock.
+
+If M-FILE is not provided, loop comparing all
+ ./test-matlab-ts-mode-font-lock-files/*.m
+
+For example, given foo.m containing
+ function a = foo
+ a = 1;
+ end
+we'll have expected that looks like
+ kkkkkkkk v d fff
+ d d dd
+ kkk
+
+For debugging, you can run with a specified M-FILE,
+ M-: (test-matlab-ts-mode-font-lock
\"test-matlab-ts-mode-font-lock-files/M-FILE\")"
+
+ (when (or (< emacs-major-version 30)
+ (not (progn
+ (require 'treesit)
+ (when (fboundp 'treesit-ready-p)
+ (treesit-ready-p 'matlab t)))))
+ (message "skipping-test: test-matlab-ts-mode-font-lock.el - matlab tree
sitter not available.")
+ (cl-return-from test-matlab-ts-mode-font-lock))
+
+ (let* ((m-files (if m-file
+ (progn
+ (setq m-file (file-truename m-file))
+ (when (not (file-exists-p m-file))
+ (error "File %s does not exist" m-file))
+ (list m-file))
+ (test-matlab-ts-mode-font-lock-files)))
+ (code-to-face '(
+ ("b" . font-lock-bracket-face)
+ ("B" . font-lock-builtin-face)
+ ("c" . font-lock-comment-face)
+ ("C" . font-lock-comment-delimiter-face)
+ ("d" . default)
+ ("D" . font-lock-delimiter-face)
+ ("f" . font-lock-function-name-face)
+ ("h" . font-lock-doc-face) ;; function doc help
comment
+ ("H" . matlab-ts-comment-heading-face) ;; %% comment
heading
+ ("k" . font-lock-keyword-face)
+ ("n" . font-lock-constant-face) ;; numbers
+ ("s" . font-lock-string-face)
+ ("S" . matlab-ts-string-delimiter-face)
+ ("p" . matlab-ts-pragma-face)
+ ("t" . font-lock-type-face)
+ ("v" . font-lock-variable-name-face)
+ ("w" . font-lock-warning-face)
+ ))
+ (face-to-code (mapcar (lambda (pair)
+ (cons (cdr pair) (car pair)))
+ code-to-face)))
+ (dolist (m-file m-files)
+ (save-excursion
+ (message "START: test-matlab-ts-mode-font-lock %s" m-file)
+
+ (when (boundp 'treesit-font-lock-level)
+ (setq treesit-font-lock-level 4))
+
+ (find-file m-file)
+
+ ;; Force font lock to throw catchable errors.
+ (font-lock-mode 1)
+ (font-lock-flush (point-min) (point-max))
+ (font-lock-ensure (point-min) (point-max))
+
+ (goto-char (point-min))
+ (let* ((got "")
+ (expected-file (replace-regexp-in-string "\\.m$" "_expected.txt"
+ m-file))
+ (got-file (concat expected-file "~"))
+ (expected (when (file-exists-p expected-file)
+ (with-temp-buffer
+ (insert-file-contents-literally expected-file)
+ (buffer-string)))))
+ (while (not (eobp))
+ (let* ((face (if (face-at-point) (face-at-point) 'default))
+ (code (if (looking-at "\\([ \t\n]\\)")
+ (match-string 1)
+ (cdr (assoc face face-to-code)))))
+ (when (not code)
+ (error "Face, %S, is not in face-to-code alist" face))
+ (setq got (concat got code))
+ (forward-char)
+ (when (looking-at "\n")
+ (setq got (concat got "\n"))
+ (forward-char))))
+
+ (when (not (string= got expected))
+ (let ((coding-system-for-write 'raw-text-unix))
+ (write-region got nil got-file))
+ (when (not expected)
+ (error "Baseline for %s does not exists. \
+See %s and if it looks good rename it to %s"
+ m-file got-file expected-file))
+ (when (= (length got) (length expected))
+ (let* ((diff-idx (1- (compare-strings got nil nil expected nil
nil)))
+ (got-code (substring got diff-idx (1+ diff-idx)))
+ (got-face (cdr (assoc got-code code-to-face)))
+ (expected-code (substring expected diff-idx (1+
diff-idx)))
+ (expected-face (cdr (assoc expected-code code-to-face))))
+ (error "Baseline for %s does not match, got: %s, expected: %s.
\
+Difference at column %d (got code-to-face \"%s\" . %S, expected code-to-face
\"%s\" . %S"
+ m-file got-file expected-file
+ diff-idx
+ got-code got-face
+ expected-code expected-face)))
+ (error "Baseline for %s does not match, lengths are different,
got: %s, expected: %s"
+ m-file got-file expected-file))
+ (kill-buffer)))
+ (message "PASS: test-matlab-ts-mode-font-lock %s" m-file)))
+ "success")
+
+(provide 'test-matlab-ts-mode-font-lock)
+;;; test-matlab-ts-mode-font-lock.el ends here