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

Reply via email to