branch: externals/matlab-mode
commit 04b58e9f134541e7e818937eaf1733047496a1bc
Author: John Ciolfi <john.ciolfi...@gmail.com>
Commit: John Ciolfi <john.ciolfi...@gmail.com>

    matlab-ts-mode: add initial support for code indenting
---
 contributing/treesit-mode-how-to.org               | 245 +++++++++++++-
 matlab-ts-mode.el                                  | 361 ++++++++++++++++++--
 tests/metest.el                                    |   4 +-
 tests/test-matlab-ts-mode-font-lock.el             |  32 +-
 .../test-matlab-ts-mode-indent-files/indent_cell.m |  33 ++
 .../indent_cell_expected.m                         |  33 ++
 .../indent_comments.m                              |  47 +++
 .../indent_comments_expected.m                     |  47 +++
 .../indent_cont_statements.m                       |  41 +++
 .../indent_cont_statements_expected.m              |  41 +++
 .../indent_copyright.m                             |  13 +
 .../indent_copyright_expected.m                    |  13 +
 .../indent_copyright_in_code.m                     |  17 +
 .../indent_copyright_in_code_expected.m            |  17 +
 .../indent_fcn_calls.m                             |  21 ++
 .../indent_fcn_calls_expected.m                    |  21 ++
 .../indent_fcn_ellipsis.m                          |  28 ++
 .../indent_fcn_ellipsis_expected.m                 |  28 ++
 .../indent_function.m                              |   7 +
 .../indent_function_expected.m                     |   7 +
 .../indent_if_continued.m                          |  42 +++
 .../indent_if_continued_expected.m                 |  42 +++
 .../indent_if_else.m                               |   7 +
 .../indent_if_else_expected.m                      |   7 +
 .../indent_keywords.m                              |  76 +++++
 .../indent_keywords_expected.m                     |  76 +++++
 .../indent_matrix.m                                |  33 ++
 .../indent_matrix_expected.m                       |  33 ++
 .../indent_nested.m                                |  29 ++
 .../indent_nested_expected.m                       |  29 ++
 .../indent_old_indents.m                           | 375 +++++++++++++++++++++
 .../indent_old_indents_expected.m                  | 375 +++++++++++++++++++++
 .../indent_ranges.m                                |  15 +
 .../indent_ranges_expected.m                       |  15 +
 .../indent_switch.m                                |  25 ++
 .../indent_switch_expected.m                       |  25 ++
 .../indent_tab_between_fcns.m                      |   6 +
 .../indent_tab_between_fcns_expected.m             |   6 +
 .../indent_tab_in_fcn.m                            |   5 +
 .../indent_tab_in_fcn_expected.m                   |   5 +
 tests/test-matlab-ts-mode-indent.el                | 166 +++++++++
 41 files changed, 2388 insertions(+), 60 deletions(-)

diff --git a/contributing/treesit-mode-how-to.org 
b/contributing/treesit-mode-how-to.org
index 7066b62b89..bc2b747d12 100644
--- a/contributing/treesit-mode-how-to.org
+++ b/contributing/treesit-mode-how-to.org
@@ -16,22 +16,25 @@
 # | 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.
+# |   Guidelines for writting a major mode powered by tree-sitter
 
-#+title: MATLAB and Tree-Sitter
+#+title: Tree-Sitter How To
 #+author: John Ciolfi
 #+date: Jun-13-2025
 
-* Overview
+* TODO
 
-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.
+- [ ] Add how to setup comments and syntax table
+- [ ] Add indent assert rule
+- [ ] Add font-lock test
+- [ ] Add indent test  
 
 * Guide to building a tree-sitter mode
 
-** Syntax trees and queries
+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.
+
+* 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
@@ -68,12 +71,12 @@ tree is roughly 10 times the text size of the program being 
analyzed. However, t
 tree sitter are highly accuracte and fast syntax coloring (font-lock), 
indentation, code
 navigation via syntatic expressions, etc.
 
-** Documentation
+* 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]]
 
-** libtree-sitter-LANGUAGE.EXT
+* libtree-sitter-LANGUAGE.EXT
 
 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). There are other locations 
that this can
@@ -117,7 +120,39 @@ You should now be able to use:
 : M-x treesit-inspect-mode
 : M-x treesit-explore-mode
 
-** Setup font-lock
+* Debugging tips
+
+- Incremental updates to your LANGUAGE-ts-mode
+
+   As you update =LANUGAGE-ts-mode.el= you need to tell Emacs to pickup the 
updates. To do this,
+
+    - Use *=C-x C-e=*. With the cursor =(point)= at the end of the syntatic 
expression of your *.el
+      file and run =C-x C-e= (or =M-x eval-last-sexp=) to evaluate the sexp 
prior to the cursor
+      point.
+
+    - Alternatively, use *=C-M-x* (or =M-x eval-defun=). With the =(point)= in 
the =defvar=,
+      =defcusom=, or =defface=, run =C-M-x= to evaluate it.
+
+   Note: =M-x eval-buffer= will not reevaluate already defined =defvar='s, so 
you must use
+   one of the above two to update a =defvar=.
+
+- =M-x LANGUAGE-ts-mode=
+
+ - After making updates to =LANGUAGE-ts-mode.el= and evaluating them, you run 
=M-x LANGUAGE-ts-mode=
+   to re-load your mode in your =test.lang= file. For example, when writing 
the indent rules, you'll
+   need to run =M-x LANGUAGE-ts-mode= after =M=x eval-defun= on in your 
=(defvar
+   LANGUAGE-ts-mode--indent-rules ....)=.
+
+- Use =M-x ielm=
+
+  In the =*ielm*= buffer created by =M-x ielm=, you can examine tree-sitter 
nodes, etc. For example:
+
+  #+begin_example
+  ELISP> (with-current-buffer "test.lang"
+        (treesit-node-parent (treesit-node-at (point))))
+  #+end_example
+
+* 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
@@ -181,14 +216,15 @@ and keywords.
       ;; 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.
+      ;; 'definition for function definitions, 'keyword for language keywords, 
etc. Below
+      ;; we have a few examples. You can use any names for your features.
       ;; 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)
+               '((comment definition)
                     (keyword string type)
-                    (builtin constant escape-sequence label number)
-                    (bracket delimiter error function operator property 
variable)))
+                    (number)
+                    (bracket delimiter error)))
 
       (treesit-major-mode-setup)))
 #+end_src
@@ -198,6 +234,154 @@ applied to the items captured by the query. You can see 
available faces by using
 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.
 
+* Comments
+
+TODO
+
+* Indent
+
+Tree-sitter indentation is controlled by =treesit-simple-indent-rules=.  We 
create a variable
+containing our N indent rules and tell tree-sitter about them
+
+#+begin_src emacs-lisp
+  (defvar LANGUAGE-ts-mode--indent-rules
+      `((LANGUAGE
+         (MATCHER-1 ANCHOR-1 OFFSET-1)
+         (MATCHER-N ANCHOR-N OFFSET-N)))
+      "Tree-sitter indent rules for `LANGUAGE-ts-mode'.")
+
+  ;;;###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)
+
+      ;; Indent
+      (setq-local treesit-simple-indent-rules LANGUAGE-ts-mode--indent-rules)
+
+      (treesit-major-mode-setup)))
+#+end_src
+
+To write the indent rules, we need to define the /matcher/, /anchor/, and 
/offset/ of each rule as
+explained in the Emacs manual, 
"[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Parser_002dbased-Indentation.html][Parser-based
 Indentation]]".  The /matcher/ and /anchor/ are are
+functions that take three arguments, =node=, =parent= node, and =bol=.  =bol= 
is the
+beginning-of-line buffer position. /matcher/ returns non-nil when the rule 
applies and /anchor/
+returns the buffer position which along with /offset/ determine the indent 
level of the line.
+
+Let's take this basic example of our LANGUAGE, =if_else.lang= file
+
+#+begin_example
+  if a > 1
+      b = a * 2;
+  else    
+      b = a;
+  end
+#+end_example
+
+Running =M-x treesit-explore-mode= gives us:
+
+#+begin_example
+  (source_file
+   (if_statement if
+    condition: (comparison_operator (identifier) > (number))
+    \n
+    (block
+     (assignment left: (identifier) =
+      right: (binary_operator left: (identifier) * right: (number)))
+     ;)
+    (else_clause else \n
+     (block
+      (assignment left: (identifier) = right: (identifier))
+      ;))
+    end)
+   \n)
+#+end_example
+
+We start with
+
+#+begin_src emacs-lisp
+  (defvar tmp-debug-indent-rule
+    '((lambda (node parent bol)
+      (message "-->N:%S P:%S BOL:%S GP:%S NPS:%S"
+               node parent bol
+               (treesit-node-parent parent)
+               (treesit-node-prev-sibling node))
+        nil)
+      nil
+      0))
+
+  (defvar LANGUAGE-ts-mode--indent-rules
+    `((LANGUAGE
+       ,tmp-debug-indent-rule
+       ((parent-is "^source_file$") column-0 0)
+       ))
+    "Tree-sitter indent rules for `LANGUAGE-ts-mode'.")
+#+end_src
+
+We set
+
+: M-: (setq treesit--indent-verbose t)
+
+and then hit the =TAB= key when vising a our =if_else.lang= file.
+
+The first rule, =((parent-is "source_file") column-0 0)= is the rule for the 
root node, which in our
+LANGUAGE is "source_file" and says to sart on column 0.
+
+The two lambda debugging rules aid in writing rules will be removed when we 
have completed the
+rules.  For example, with the above and we type =TAB= on the "b = a * 2" line 
in the following
+=if_else.lang= file.
+
+#+begin_example
+  if a > 1
+      b = a * 2;
+  else    
+      b = a;
+  end
+#+end_example
+
+we'll see in the =*Messages*= buffer we'll see the error:
+
+ : node: #<treesit-node block in 14-24> parent: #<treesit-node if_statement in 
1-44> bol: 14
+
+where point 14-24 is "b = a * 2" and we see it has node named "block". Thus, 
we update we add to our
+indent rules, =((node-is "block") parent 4)= and a couple more rules as shown 
below.
+
+*Tip*: =C-M-x= in our =defvar= and re-run =M-x LANGUAGE-ts-mode= file to 
pickup the new indent
+rules.
+
+#+begin_src emacs-lisp
+  (defvar LANGUAGE-ts-mode--indent-rules
+    `((LANGUAGE
+       ,tmp-debug-indent-rule
+       ((parent-is "^source_file$") column-0 0)
+       ((node-is "^block$") parent 4)
+       ((node-is "^else_clause$") parent 0)
+       ((node-is "%end$") parent 0)
+       ))
+    "Tree-sitter indent rules for `LANGUAGE-ts-mode'.")
+#+end_src
+
+We can simplify this because the "else_clause" and "end" nodes have the same 
indent rules:
+
+#+begin_src emacs-lisp
+  (defvar LANGUAGE-ts-mode--indent-rules
+    `((LANGUAGE
+       ,tmp-debug-indent-rule
+       ((parent-is "^source_file$") column-0 0)
+       ((node-is "^block$") parent 4)
+       ((node-is ,(rx bol (or "else_clause" "end") eol)) parent 0)
+       ))
+    "Tree-sitter indent rules for `LANGUAGE-ts-mode'.")
+#+end_src
+
+Following this process, we add additional rules and our indent engine is 
complete after we remove
+the debugging rules.
+
+*Tip*: If you look at the defintion, =M-x find-variable RET 
treesit-simple-indent-presets RET=, you
+can see how the built-in /matchers/ and /achors/ are written. From that, you 
can write your own as
+needed.
+
 * Issues
 
 - [ ] Building libtree-sitter-matlab.dll from src on Windows produces a DLL 
that fails.
@@ -241,3 +425,34 @@ avoid dependenices on other packages or create your own 
face.
 
   makes it easy to install packages from ELPA, MELPA, etc. but how to we get
   libtree-sitter-LANUGAGE.EXT (EXT = .so, .dll, .dylib) installed?
+
+- [ ] In 
[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Parser_002dbased-Indentation.html][Parser-Based
 Indentation]] we have prev-line which goes backward exactly one line
+
+  Consider a programming lanugage with a few statements, e.g.
+
+  #+begin_example
+    {
+        a = 1;
+        b = 2;
+
+
+    }
+  #+end_example
+
+  If you use prev-line on the blank-line immediately after "b = 2;", you'll 
get the expected
+  point below "b". If you use prev-line on the second blank line after "b = 
2;", you'll get
+  0, which is unexpected in many languages. I suspect it may be safe to just 
update prev-real
+  line too look backwards to the first prior line with non-whitespace or if 
you are worried
+  about compatibility, introduce:
+
+  #+begin_src emacs-lisp
+    (cons 'prev-real-line (lambda (_n _p bol &rest _)
+                       (save-excursion
+                         (goto-char bol)
+                         (forward-line -1)
+                         (while (and (not (bobp))
+                                     (looking-at "^[ \t]*$"))
+                           (forward-line -1))
+                         (skip-chars-forward " \t")
+                         (point))))
+  #+end_src
diff --git a/matlab-ts-mode.el b/matlab-ts-mode.el
index 2abba95231..95ad09ca0c 100644
--- a/matlab-ts-mode.el
+++ b/matlab-ts-mode.el
@@ -63,6 +63,13 @@
        :bold t))
   "Face for \"%% code section\" headings when NOT in 
matlab-sections-minor-mode.")
 
+(defcustom matlab-ts-font-lock-level 4
+  "Level of font lock, 1 for minimum syntax highlighting and 4 for maximum."
+  :type '(choice (const :tag "Minimal" 1)
+                (const :tag "Low" 2)
+                (const :tag "Standard" 3)
+                (const :tag "Standard plus parse errors" 4)))
+
 ;;--------------------;;
 ;; Section: font-lock ;;
 ;;--------------------;;
@@ -78,7 +85,7 @@
   ;; 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.
+  ;; correctly handles them by letting us look for these as content of the 
nodes.
   '("arguments"
     (break_statement)
     "case"
@@ -118,48 +125,64 @@
     "uint64")
   "MATLAB data type functions.")
 
-(defun matlab-ts-mode--doc-comment-capture (comment-node override start end 
&rest _)
-  "Fontify function/classdef documentation comments.
+(defun matlab-ts-mode--is-doc-comment (comment-node parent)
+  "Is the COMMENT-NODE under PARENT a help doc comment.
 In MATLAB,
+
+  function out = myFunction
+  % The documentation help comment for myFunction immediately follows the
+  % function defintion.
+
+      % code comments are preceded with a blank line
+      out = 1;
+  end
+
   function out = myFunction
   % The documentation help comment for myFunction immediately follows the
   % function defintion.
 
-      % code comments are preceeded with a blank line
+  % copyright at column 0 and preceded by blank liness after the help comment
+
+      % code comments are preceded with a blank line
       out = 1;
   end
 
   function out = myFunctionWithoutHelp
 
-      % code comments are preceeded with a blank line
+      % code comments are preceded with a blank line
       out = 1;
   end
 
+Similar behavior for classdef's."
+
+  (when (string-match-p (rx bol (or "function_definition" "class_definition") 
eol)
+                        (treesit-node-type parent))
+    (let ((definition-point (treesit-node-start parent)))
+      (save-excursion
+        (goto-char (treesit-node-start comment-node))
+        (beginning-of-line)
+
+        ;; Skip backwards over the copyright line to prior content.
+        (when (looking-at "^[ \t]*%[ \t]*copyright\\b")
+          (beginning-of-line)
+          (forward-line -1)
+          (while (looking-at "^[ \t]*$")
+            (forward-line -1)))
+
+        ;; result - is doc comment?
+        (or (<= (point) definition-point) ;; at definition?
+            (and (> (point) definition-point)
+                 (not (re-search-backward "^[ \t]*$" definition-point t))))))))
+
+(defun matlab-ts-mode--doc-comment-capture (comment-node override start end 
&rest _)
+  "Fontify function/classdef documentation comments.
 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))))
+  (when (matlab-ts-mode--is-doc-comment comment-node (treesit-node-parent 
comment-node))
+    (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
@@ -195,9 +218,9 @@ START and END specify the region to be fontified."
      (function_arguments arguments:
                          (identifier)      @font-lock-variable-name-face
                          ("," (identifier) @font-lock-variable-name-face) :*)
-     ;; Function single output arugment: function out = functionName(in1, in2)
+     ;; Function single output argument: function out = functionName(in1, in2)
      (function_output (identifier) @font-lock-variable-name-face)
-     ;; Function multiple ouptut arguments: function [out1, out2] = 
functionName(in1, in2)
+     ;; Function multiple output arguments: function [out1, out2] = 
functionName(in1, in2)
      (function_output (multioutput_variable (identifier) 
@font-lock-variable-name-face))
      ;; Fields of: arguments ... end , properties ... end
      (property (validation_functions (identifier) @font-lock-builtin-face))
@@ -263,6 +286,255 @@ START and END specify the region to be fontified."
    )
   "MATLAB tree-sitter font-lock settings.")
 
+
+;;-----------------:;
+;; Section: Indent ;;
+;;-----------------;;
+
+;; We discourage customizing the indentation rules. Having one-style of 
consistent indentation makes
+;; reading others' code easier.
+(defvar matlab-ts-mode--indent-level 4
+  "Indentation level.")
+(defvar matlab-ts-mode--switch-indent-level (/ matlab-ts-mode--indent-level 2)
+  "Indentation level for switch-case statements.")
+(defvar matlab-ts-mode--array-indent-level 2
+  "Indentation level for elements in an array.")
+
+(defun matlab-ts-mode--prev-real-line (_n _p bol &rest _)
+  "Return point of first non-whitespace looking backward.
+BOL, beginning-of-line point, is where to start from."
+  (save-excursion
+    (goto-char bol)
+    (forward-line -1)
+    (while (and (not (bobp))
+                (looking-at "^[ \t]*$"))
+      (forward-line -1))
+    (skip-chars-forward " \t")
+    (point)))
+
+(defun matlab-ts-mode--prev-real-line-is (node-type prev-real-line-node-type)
+  "Node type matcher and previous real line type matcher.
+Returns non-nil if the current tree-sitter node matches NODE-TYPE and
+the previous non-empty line tree-sitter node type matches
+PREV-REAL-LINE-NODE-TYPE.  NODE-TYPE can be nil when there's no current
+node or a regular expression.  PREV-REAL-LINE-NODE-TYPE is a regular
+expression."
+  (lambda (node parent bol &rest _)
+    (when (or (and (not node-type)
+                   (not node))
+              (and node-type
+                   (string-match-p node-type (or (treesit-node-type node) 
""))))
+      (let* ((prev-real-line-bol (matlab-ts-mode--prev-real-line node parent 
bol))
+             (p-node (treesit-node-at prev-real-line-bol)))
+        (string-match-p prev-real-line-node-type (or (treesit-node-type 
p-node) ""))))))
+
+(defvar tmp-debug-indent-rule
+  '((lambda (node parent bol)
+      (message "-->N:%S P:%S BOL:%S GP:%S NPS:%S"
+               node parent bol
+               (treesit-node-parent parent)
+               (treesit-node-prev-sibling node))
+      nil)
+    nil
+    0))
+
+(defvar matlab-ts-mode--indent-assert nil
+  "Tests should set this to t to identify when we fail to find an indent 
rule.")
+
+(defvar matlab-ts-mode--indent-assert-rule
+  '((lambda (node parent bol)
+      (when matlab-ts-mode--indent-assert
+        (error "Assert no indent rule for: N:%S P:%S BOL:%S GP:%S NPS:%S 
BUF:%S"
+               node parent bol
+               (treesit-node-parent parent)
+               (treesit-node-prev-sibling node)
+               (buffer-name))))
+    nil
+    0))
+
+(defvar matlab-ts-mode--indent-rules
+  `((matlab
+
+     ;; ,tmp-debug-indent-rule
+
+     ;; Rule: classdef's, function's, or code for a script that is at the 
top-level
+     ((parent-is ,(rx bol "source_file" eol)) column-0 0)
+
+     ;; Rule: within a function/classdef doc block comment "%{ ... %}"?
+     ((lambda (node parent bol &rest _)
+        (and (not node)
+             (string= "comment" (treesit-node-type parent))
+             (not (save-excursion (goto-char bol)
+                                  (looking-at "%")))
+             (matlab-ts-mode--is-doc-comment parent (treesit-node-parent 
parent))))
+      parent 2)
+
+     ;; Rule: function/classdef doc comment?
+     ((lambda (node parent &rest _)
+        (or (and (string= "comment" (or (treesit-node-type node) ""))
+                 (matlab-ts-mode--is-doc-comment node parent))
+            (and (not node)
+                 (string= "comment" (treesit-node-type parent))
+                 (matlab-ts-mode--is-doc-comment parent (treesit-node-parent 
parent)))))
+      parent 0)
+
+     ;; Rule: within a code block comment "%{ ... %}"?
+     ((lambda (node parent bol &rest _)
+        (and (not node)
+             (string= "comment" (treesit-node-type parent))
+             (not (save-excursion (goto-char bol)
+                                  (looking-at "%")))))
+      parent
+      2)
+
+     ;; Rule: last line of code block coment "%{ ... %}"?
+     ((lambda (node parent bol &rest _)
+        (and (not node)
+             (string= "comment" (treesit-node-type parent))
+             (save-excursion (goto-char bol)
+                             (looking-at "%"))))
+      parent
+      0)
+
+     ;; Rule: switch case and otherwise statements
+     ((node-is ,(rx bol (or "case_clause" "otherwise_clause") eol))
+      parent ,matlab-ts-mode--switch-indent-level)
+
+     ;; Rule: first line of code witin a switch case or otherwise statement, 
node is block
+     ((parent-is ,(rx bol (or "case_clause" "otherwise_clause") eol))
+      parent ,matlab-ts-mode--switch-indent-level)
+
+     ;; Rule: nested functions
+     ((n-p-gp ,(rx bol "function_definition" eol) ,(rx bol "block" eol)
+              ,(rx bol "function_definition" eol))
+      parent 0)
+
+     ;; Rule: constructs within classdef or function's.
+     ((node-is ,(rx bol (or "arguments_statement" "block" "enumeration" "enum" 
"methods" "events"
+                            "function_definition" "property" "properties")
+                    eol))
+      parent ,matlab-ts-mode--indent-level)
+
+     ;; Rule: elseif, else, catch, end statements go back to parent level
+     ((node-is ,(rx bol (or "elseif_clause" "else_clause" "catch_clause" 
"end") eol)) parent 0)
+
+     ;; Rule: code in if, for, methods, function, arguments statements
+     ((parent-is ,(rx bol (or "if_statement" "for_statement" "while_statement"
+                              "methods" "events" "enumeration"
+                              "function_definition" "arguments_statement")
+                      eol))
+      parent ,matlab-ts-mode--indent-level)
+
+     ;; Rule: function a<RET>
+     ;;       end
+     ((n-p-gp nil ,(rx bol "\n" eol) ,(rx bol (or "function_definition") eol))
+      grand-parent ,matlab-ts-mode--indent-level)
+
+     ;; Rule: case 10<RET>
+     ((n-p-gp nil ,(rx bol "\n" eol) ,(rx bol (or "switch_statement" 
"case_clause"
+                                                  "otherwise_clause")
+                                          eol))
+      grand-parent ,matlab-ts-mode--switch-indent-level)
+
+     ;; Rule:  if condition1 || ...     |      if condition1 + condition2 == 
...
+     ;; <TAB>     condition2 || ...     |         2770000 ...
+     ((parent-is ,(rx bol (or "boolean_operator" "comparison_operator") eol)) 
parent 0)
+
+     ;; Rule:  elseif ...
+     ;; <TAB>      condition2 || ...
+     ((parent-is ,(rx bol (or "else_clause" "elseif_clause") eol))
+      parent ,matlab-ts-mode--indent-level)
+     
+     ;; Rule: if a ...
+     ;; <TAB>
+     ((n-p-gp nil ,(rx bol "\n" eol)
+              ,(rx bol (or "if_statement" "else_clause" "elseif_clause" ) eol))
+      grand-parent ,matlab-ts-mode--indent-level)
+
+     ;; Rule: disp(myMatrix(1:  ...
+     ;; <TAB>               end)); 
+     ((parent-is ,(rx bol "range" eol)) parent 0)
+
+     ;; Rule: try<RET>    |   catch<RET>
+     ((parent-is ,(rx bol (or "try_statement" "catch_clause") eol))
+      parent ,matlab-ts-mode--indent-level)
+
+     ;; Rule: function a
+     ;;           x = 1;
+     ;; <TAB>     y = 2;
+     ((parent-is ,(rx bol "block" eol)) parent 0)
+
+     ;; Rule: "switch var" and we type RET after the var
+     (,(matlab-ts-mode--prev-real-line-is (rx bol "\n" eol) (rx bol "switch" 
eol))
+      ,#'matlab-ts-mode--prev-real-line ,matlab-ts-mode--switch-indent-level)
+
+     ;; Rule: "function foo()" and we type RET after the ")"
+     (,(matlab-ts-mode--prev-real-line-is nil (rx bol "function" eol))
+      ,#'matlab-ts-mode--prev-real-line ,matlab-ts-mode--indent-level)
+
+     ;; Rule:  a = ...
+     ;; <TAB>      1;
+     ((parent-is ,(rx bol "assignment" eol)) parent 
,matlab-ts-mode--indent-level)
+
+     ;; Rule:  a = 2 * ...
+     ;; <TAB>      1;
+     ((parent-is ,(rx bol "binary_operator" eol)) parent 0)
+
+     ;; Rule:  a = ( ...       |     a = [  ...     |     a = {    ...
+     ;;             1 ...      |          1 ...     |            1 ...
+     ;; <TAB>      );          |         ];         |         };
+     ((node-is ,(rx bol (or ")" "]" "}") eol)) parent 0)
+
+     ;; Rule:  a = ( ...
+     ;; <TAB>       1 ...
+     ((parent-is ,(rx bol "parenthesis" eol)) parent 1)
+
+     ;; Rule:  a = [   ...    |    a = { ...
+     ;; <TAB>        2 ...    |          2 ...
+     ((parent-is ,(rx bol (or "matrix" "cell") eol)) parent 
,matlab-ts-mode--array-indent-level)
+
+     ;; Rule:  function [   ...              |    function name (   ...
+     ;; <TAB>            a, ... % comment    |                   a, ... % 
comment
+     ((parent-is ,(rx bol (or "multioutput_variable" "function_arguments") 
eol)) parent 1)
+
+     ;; Rule:  a = [    2 ...       |   function a ...
+     ;; <TAB>           1 ...       |            = fcn
+     ((parent-is ,(rx bol (or "row" "function_output") eol)) parent 0)
+
+     ;; Rule:  a = ...
+     ;; <TAB>      1;
+     ((n-p-gp nil nil ,(rx bol "assignment" eol)) grand-parent 
,matlab-ts-mode--indent-level)
+
+     ;; Rule:  a = my_function(1, ...
+     ;; <TAB>                  2, ...
+     ((parent-is ,(rx bol "arguments" eol)) parent 0)
+
+     ;; Rule:  my_function( ...
+     ;; <TAB>      1, ...
+     ((node-is ,(rx bol "arguments" eol)) parent ,matlab-ts-mode--indent-level)
+
+     ;; Rule: function indent_tab_between_fcns   |   function indent_tab_in_fcn
+     ;;       end                                |      disp('here')
+     ;; <TAB>                                    |
+     ;;       function b                         |   end
+     ;;       end                                |
+     ((lambda (node parent bol)
+        (and (not node)
+             (string= (treesit-node-type parent) "\n")))
+      grand-parent 0)
+
+     ;; Rule: In an empty line, string, etc. just maintain indent
+     ;;       switch in
+     ;;         case 10
+     ;;           disp('11');
+     ;; <TAB>
+     (no-node ,#'matlab-ts-mode--prev-real-line 0)
+
+     ;; Rule: Assert if no rule matched and asserts are enabled.
+     ,matlab-ts-mode--indent-assert-rule
+     ))
+  "Tree-sitter indent rules for `matlab-ts-mode'.")
+
 ;;;###autoload
 (define-derived-mode matlab-ts-mode prog-mode "MATLAB:ts"
   "Major mode for editing MATLAB files with tree-sitter."
@@ -270,13 +542,40 @@ START and END specify the region to be fontified."
   (when (treesit-ready-p 'matlab)
     (treesit-parser-create 'matlab)
 
+    ;; Comments
+    ;;   TODO: M-; on code comments, then a 2nd M-; doesn't uncomment
+    ;;   likely need to also set up the syntax table.
+    (setq-local comment-start      "%")
+    (setq-local comment-end        "")
+    (setq-local comment-start-skip "%\\s-+")
+
+    ;; TODO function end handling
+    ;; TODO add strings to syntax table?
+    ;; TODO what about syntax table and electric keywords?
+    ;; TODO function / end match like matlab-mode
+    ;; TODO code folding
+    ;; TODO fill paragraph, etc. look at c-ts-common.el
+    ;; TODO outline: look at 
https://hg.sr.ht/~pranshu/perl-ts-mode/browse/perl-ts-mode.el?rev=tip
+    ;; TODO imenu: look at 
https://hg.sr.ht/~pranshu/perl-ts-mode/browse/perl-ts-mode.el?rev=tip
+    ;; TODO handle file name mismatch between function / classdef name
+    ;; TODO face for all built-in functions such as dbstop, quit, sin, etc.
+    ;;   
https://www.mathworks.com/help/matlab/referencelist.html?type=function&category=index&s_tid=CRUX_lftnav_function_index
+    ;;   
https://stackoverflow.com/questions/51942464/programmatically-return-a-list-of-all-functions/51946257
+    ;;   Maybe use completion api and complete on each letter?
+    ;;   Maybe look at functionSignatures.json?
+
     ;; Font-lock
+    (setq-local treesit-font-lock-level matlab-ts-font-lock-level)
     (setq-local treesit-font-lock-settings matlab-ts-mode--font-lock-settings)
     (setq-local treesit-font-lock-feature-list
                 '((comment definition)
                   (keyword string type)
-                  (number)
-                  (bracket delimiter error)))
+                  (number bracket delimiter)
+                  ( error)))
+
+    ;; Indent
+    (setq-local indent-tabs-mode nil ;; for consistency between Unix and 
Windows we don't use TABs.
+                treesit-simple-indent-rules matlab-ts-mode--indent-rules)
 
     (treesit-major-mode-setup)))
 
diff --git a/tests/metest.el b/tests/metest.el
index af924fd5fb..8e3d25adea 100644
--- a/tests/metest.el
+++ b/tests/metest.el
@@ -85,7 +85,9 @@
   ;; matlab-ts-mode tests
   (when (>= emacs-major-version 30)
     (require 'test-matlab-ts-mode-font-lock)
-    (metest-run 'test-matlab-ts-mode-font-lock)))
+    (metest-run 'test-matlab-ts-mode-font-lock)
+    (require 'test-matlab-ts-mode-indent)
+    (metest-run 'test-matlab-ts-mode-indent)))
 
 (defun metest-run (test)
   "Run and time TEST."
diff --git a/tests/test-matlab-ts-mode-font-lock.el 
b/tests/test-matlab-ts-mode-font-lock.el
index dca7cd9966..6a5a514b4f 100644
--- a/tests/test-matlab-ts-mode-font-lock.el
+++ b/tests/test-matlab-ts-mode-font-lock.el
@@ -1,4 +1,4 @@
-;;; test-matlab-ts-mode-font-lock.el --- Testing suite for MATLAB Emacs -*- 
lexical-binding: t -*-
+;;; test-matlab-ts-mode-font-lock.el --- Test matlab-ts-mode font-lock -*- 
lexical-binding: t -*-
 ;;
 ;; Copyright Free Software Foundation
 
@@ -17,16 +17,22 @@
 
 ;;; Commentary:
 ;;
-;;  Validate font-lock faces in matlab-ts-mode
+;; Validate matlab-ts-mode font-lock faces.
+;; Load ../matlab-ts-mode.el via require and run font-lock tests using
+;; ./test-matlab-ts-mode-font-lock-files/NAME.m comparing against
+;; ./test-matlab-ts-mode-font-lock-files/NAME_expected.txt
+;;
 
 ;;; Code:
 
+(require 'cl-macs)
+
+;; Add abs-path of ".." to load-path so we can (require 'matlab-ts-mode)
 (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 ()
@@ -37,26 +43,26 @@
   (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 font-lock using ./test-matlab-ts-mode-font-lock-files/NAME.m.
+Compare ./test-matlab-ts-mode-font-lock-files/NAME.m 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
+NAME_expected.txt is of same length as NAME.m 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
+If M-FILE NAME.m is not provided, loop comparing all
+./test-matlab-ts-mode-font-lock-files/NAME.m files.
 
-For example, given foo.m containing
+For example, given foo.m containing:
     function a = foo
         a = 1;
     end
-we'll have expected that looks like
+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\")"
+For debugging, you can run with a specified NAME.m,
+  M-: (test-matlab-ts-mode-font-lock 
\"test-matlab-ts-mode-font-lock-files/NAME.m\")"
 
   (when (or (< emacs-major-version 30)
             (not (progn
@@ -109,7 +115,7 @@ For debugging, you can run with a specified M-FILE,
         (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"
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_cell.m 
b/tests/test-matlab-ts-mode-indent-files/indent_cell.m
new file mode 100644
index 0000000000..8884883656
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_cell.m
@@ -0,0 +1,33 @@
+% -*- matlab-ts -*-
+function a = indent_cell
+    a = { ...
+      1 ...
+    + ...
+       2
+  };
+
+    a = {    2 ...
+      1 ...
+        };
+
+  a = { ...
+        2 + { 3
+          4,
+              5 + { ...
+                      2
+                }
+           }
+  };
+
+     a = { ...
+       1; ...
+     2 ...
+        };
+
+    long_variable_a = ...
+        {
+          2, 123, 456
+          3,   2    7
+        };
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_cell_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_cell_expected.m
new file mode 100644
index 0000000000..b7a2febac7
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_cell_expected.m
@@ -0,0 +1,33 @@
+% -*- matlab-ts -*-
+function a = indent_cell
+    a = { ...
+          1 ...
+          + ...
+          2
+        };
+
+    a = {    2 ...
+             1 ...
+        };
+
+    a = { ...
+          2 + { 3
+                4,
+                5 + { ...
+                      2
+                    }
+              }
+        };
+
+    a = { ...
+          1; ...
+          2 ...
+        };
+
+    long_variable_a = ...
+        {
+          2, 123, 456
+          3,   2    7
+        };
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_comments.m 
b/tests/test-matlab-ts-mode-indent-files/indent_comments.m
new file mode 100644
index 0000000000..ea2c8b88d1
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_comments.m
@@ -0,0 +1,47 @@
+% -*- matlab-ts -*-
+function b = indent_comments(a)
+% this the doc help
+% comment
+    
+    % comment about fcn1
+    [c, d] = fcn1(a, a);
+
+    b = c + d;
+
+    % comment about fcn2
+    fcn2;
+end
+
+
+function [c, d] = fcn1(a, b)
+%{
+  help comment
+
+  with blank lines
+
+%}
+    c = a;
+    d = b;
+end
+
+
+function fcn2
+%{
+  help comment
+  
+  with blank lines
+  
+    
+%}
+
+    %{
+
+      block
+      comment
+      for following
+      
+      line
+    %}
+
+    disp('2');
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_comments_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_comments_expected.m
new file mode 100644
index 0000000000..60581922ab
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_comments_expected.m
@@ -0,0 +1,47 @@
+% -*- matlab-ts -*-
+function b = indent_comments(a)
+% this the doc help
+% comment
+
+    % comment about fcn1
+    [c, d] = fcn1(a, a);
+
+    b = c + d;
+
+    % comment about fcn2
+    fcn2;
+end
+
+
+function [c, d] = fcn1(a, b)
+%{
+  help comment
+
+  with blank lines
+
+%}
+    c = a;
+    d = b;
+end
+
+
+function fcn2
+%{
+  help comment
+
+  with blank lines
+
+
+%}
+
+    %{
+
+      block
+      comment
+      for following
+
+      line
+    %}
+
+    disp('2');
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_cont_statements.m 
b/tests/test-matlab-ts-mode-indent-files/indent_cont_statements.m
new file mode 100644
index 0000000000..4622afe840
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_cont_statements.m
@@ -0,0 +1,41 @@
+% -*- matlab-ts -*-
+function a = indent_cont_statements
+
+    a = ...
+        1;
+
+    a =     ...
+        1 + ...
+        2 + ...
+        2;
+
+    a = 2 * ...
+        1;
+
+    a = (       ...
+         1 + 2);
+
+    a = (  ...
+         1 ...
+        );
+    
+    a = 2 *  ...
+        (    ...
+         3 + ...
+         4 + (    ...
+              5 * ...
+              6   ...
+             )    ...
+        ); ...
+    
+    a = 1 + 2;
+
+    % The matlab-tree-sitter, by design for simplicity treats "..." as 
comments so the following
+    % is an error in MATLAB, but doesn't generate a parse error and indents 
the same as if
+    % the ellipsis (...) were present.
+    a =
+        1 +
+        2;
+
+
+end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_cont_statements_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_cont_statements_expected.m
new file mode 100644
index 0000000000..667a88cfc4
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_cont_statements_expected.m
@@ -0,0 +1,41 @@
+% -*- matlab-ts -*-
+function a = indent_cont_statements
+
+    a = ...
+        1;
+
+    a =     ...
+        1 + ...
+        2 + ...
+        2;
+
+    a = 2 * ...
+        1;
+
+    a = (       ...
+         1 + 2);
+
+    a = (  ...
+         1 ...
+        );
+
+    a = 2 *  ...
+        (    ...
+         3 + ...
+         4 + (    ...
+              5 * ...
+              6   ...
+             )    ...
+        ); ...
+
+    a = 1 + 2;
+
+    % The matlab-tree-sitter, by design for simplicity treats "..." as 
comments so the following
+    % is an error in MATLAB, but doesn't generate a parse error and indents 
the same as if
+    % the ellipsis (...) were present.
+    a =
+        1 +
+        2;
+
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_copyright.m 
b/tests/test-matlab-ts-mode-indent-files/indent_copyright.m
new file mode 100644
index 0000000000..9ca4fd6823
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_copyright.m
@@ -0,0 +1,13 @@
+% -*- matlab-ts -*-
+    function indent_copyright
+% indent_copyright help
+  % comment
+% that is several
+   % lines long
+
+   % Copyright after help comment shouldn't be indented
+
+% this is a comment about the following code
+       disp('here')
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_copyright_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_copyright_expected.m
new file mode 100644
index 0000000000..4462551f22
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_copyright_expected.m
@@ -0,0 +1,13 @@
+% -*- matlab-ts -*-
+function indent_copyright
+% indent_copyright help
+% comment
+% that is several
+% lines long
+
+% Copyright after help comment shouldn't be indented
+
+    % this is a comment about the following code
+    disp('here')
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_copyright_in_code.m 
b/tests/test-matlab-ts-mode-indent-files/indent_copyright_in_code.m
new file mode 100644
index 0000000000..e03408228d
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_copyright_in_code.m
@@ -0,0 +1,17 @@
+% -*- matlab-ts -*-
+   function b = indent_copyright_in_code
+   %{
+     sadf
+   
+         asdfasd     
+     %}
+       % foo
+    % foo
+   
+       
+       
+    % copyright blah
+   
+     % foo
+          b = 1;
+ end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_copyright_in_code_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_copyright_in_code_expected.m
new file mode 100644
index 0000000000..ba21d358ea
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_copyright_in_code_expected.m
@@ -0,0 +1,17 @@
+% -*- matlab-ts -*-
+function b = indent_copyright_in_code
+%{
+  sadf
+
+  asdfasd
+%}
+    % foo
+    % foo
+
+
+
+    % copyright blah
+
+    % foo
+    b = 1;
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_fcn_calls.m 
b/tests/test-matlab-ts-mode-indent-files/indent_fcn_calls.m
new file mode 100644
index 0000000000..d0287826b5
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_fcn_calls.m
@@ -0,0 +1,21 @@
+% -*- matlab-ts -*-
+var_a = my_function(1, 2, 3);
+
+var_b = my_function(1, ...
+                    2, ...
+                    3);
+
+my_struct.var_c = my_function( ...
+    1, ...
+    2, ...
+    3);
+
+my_other_function(1, ...
+                  2, ...
+                  3);
+
+% some extra spaces after a function call
+my_other_function  ( ...
+    1, ...
+    2, ...
+    3);
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_fcn_calls_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_fcn_calls_expected.m
new file mode 100644
index 0000000000..d0287826b5
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_fcn_calls_expected.m
@@ -0,0 +1,21 @@
+% -*- matlab-ts -*-
+var_a = my_function(1, 2, 3);
+
+var_b = my_function(1, ...
+                    2, ...
+                    3);
+
+my_struct.var_c = my_function( ...
+    1, ...
+    2, ...
+    3);
+
+my_other_function(1, ...
+                  2, ...
+                  3);
+
+% some extra spaces after a function call
+my_other_function  ( ...
+    1, ...
+    2, ...
+    3);
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_fcn_ellipsis.m 
b/tests/test-matlab-ts-mode-indent-files/indent_fcn_ellipsis.m
new file mode 100644
index 0000000000..6e040266a9
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_fcn_ellipsis.m
@@ -0,0 +1,28 @@
+% -*- matlab-ts -*-
+function ...
+    [    ...
+     a,  ... comment about a
+     b   ... comment about b
+    ]    ...
+    = indent_ellipsis ...
+    (   ...
+     c, ...  comment about c
+     d, ...  comment about d
+     e  ...  comment about e
+    )
+
+    a =     ...
+        1 + ...
+        (   ...
+         c ...
+         * ...
+         d ...
+        );
+
+    b = [ ...
+          1 + ...
+          2 + ...
+          3   ...
+        ];
+
+end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_fcn_ellipsis_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_fcn_ellipsis_expected.m
new file mode 100644
index 0000000000..6e040266a9
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_fcn_ellipsis_expected.m
@@ -0,0 +1,28 @@
+% -*- matlab-ts -*-
+function ...
+    [    ...
+     a,  ... comment about a
+     b   ... comment about b
+    ]    ...
+    = indent_ellipsis ...
+    (   ...
+     c, ...  comment about c
+     d, ...  comment about d
+     e  ...  comment about e
+    )
+
+    a =     ...
+        1 + ...
+        (   ...
+         c ...
+         * ...
+         d ...
+        );
+
+    b = [ ...
+          1 + ...
+          2 + ...
+          3   ...
+        ];
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_function.m 
b/tests/test-matlab-ts-mode-indent-files/indent_function.m
new file mode 100644
index 0000000000..a22813aab5
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_function.m
@@ -0,0 +1,7 @@
+% -*- matlab-ts -*-
+function b = indent_function(a)
+% doc comment
+
+    % code comment
+    b = a;
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_function_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_function_expected.m
new file mode 100644
index 0000000000..a22813aab5
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_function_expected.m
@@ -0,0 +1,7 @@
+% -*- matlab-ts -*-
+function b = indent_function(a)
+% doc comment
+
+    % code comment
+    b = a;
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_if_continued.m 
b/tests/test-matlab-ts-mode-indent-files/indent_if_continued.m
new file mode 100644
index 0000000000..b33f6f1d21
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_if_continued.m
@@ -0,0 +1,42 @@
+% -*- matlab-ts -*-
+function indent_if_continued
+
+    if condition1 || ...
+       condition2 || ...
+       fcn_call(arg1, ... 
+                arg2)
+
+        line_in_if();
+
+    elseif condition1 + condition2 == ...
+           2770000 ...
+        fcn_call(arg1, ... 
+                 arg2)  
+        line_in_if();
+    elseif (condition2 || ...
+            (condition3 && ...
+             condition4))
+        disp('hello')
+    elseif ...
+        condition2 || ...
+        (condition3 && ...
+         condition4)
+
+        disp('hello')
+    else ...
+        
+
+    end
+
+    if  a
+        
+
+    end
+
+    if    ...
+        foo + ...
+        bar
+
+    end
+
+end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_if_continued_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_if_continued_expected.m
new file mode 100644
index 0000000000..3247d19446
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_if_continued_expected.m
@@ -0,0 +1,42 @@
+% -*- matlab-ts -*-
+function indent_if_continued
+
+    if condition1 || ...
+       condition2 || ...
+       fcn_call(arg1, ...
+                arg2)
+
+        line_in_if();
+
+    elseif condition1 + condition2 == ...
+           2770000 ...
+        fcn_call(arg1, ...
+                 arg2)
+        line_in_if();
+    elseif (condition2 || ...
+            (condition3 && ...
+             condition4))
+        disp('hello')
+    elseif ...
+        condition2 || ...
+        (condition3 && ...
+         condition4)
+
+        disp('hello')
+    else ...
+
+
+    end
+
+    if  a
+
+
+    end
+
+    if    ...
+        foo + ...
+        bar
+
+    end
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_if_else.m 
b/tests/test-matlab-ts-mode-indent-files/indent_if_else.m
new file mode 100644
index 0000000000..dc0f308d79
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_if_else.m
@@ -0,0 +1,7 @@
+% -*- matlab-ts -*-
+if a > 1
+    b = a * 2;
+    b = b + 1;
+else
+    b = a;
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_if_else_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_if_else_expected.m
new file mode 100644
index 0000000000..dc0f308d79
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_if_else_expected.m
@@ -0,0 +1,7 @@
+% -*- matlab-ts -*-
+if a > 1
+    b = a * 2;
+    b = b + 1;
+else
+    b = a;
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_keywords.m 
b/tests/test-matlab-ts-mode-indent-files/indent_keywords.m
new file mode 100644
index 0000000000..889a0c4470
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_keywords.m
@@ -0,0 +1,76 @@
+% -*- matlab-ts -*-
+classdef indent_keywords
+    properties
+        p1 = 0
+        p2 = 0
+    end
+    methods
+        function method1(in)
+            
+            arguments
+                in (1,1) double
+            end 
+        
+            global gVar1 gVar2
+            global pVar1 pVar2
+            
+            try
+
+                
+                switch in
+                  case 10
+                    disp('10');
+
+
+                    disp('11');
+                  otherwise
+                    
+                    disp('~10');
+                
+                end
+            catch me
+            
+                rethrow(me)
+            end
+            
+            j = 0;
+            for n = 1:in
+                if mod(n, 5)
+                    x = 1;
+                    continue
+                elseif mod(n, 7)
+                    continue
+                else
+                    j = j + 1;
+                end
+                disp(['Divisible by 5 or 7 : ' num2str(n)])
+            end
+            
+            x = 0
+            while x < 10
+                x = x + 1;
+            end
+        end
+
+        function method2()
+            n = 200;
+            A = 500;
+            a = zeros(1,n);
+            parfor i = 1:n
+                a(i) = max(abs(eig(rand(A))));
+            end
+            
+            return
+        end
+    end
+
+    events
+        e1
+        e2
+    end
+
+    enumeration
+        one
+        two
+    end 
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_keywords_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_keywords_expected.m
new file mode 100644
index 0000000000..bcabb8b9f7
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_keywords_expected.m
@@ -0,0 +1,76 @@
+% -*- matlab-ts -*-
+classdef indent_keywords
+    properties
+        p1 = 0
+        p2 = 0
+    end
+    methods
+        function method1(in)
+
+            arguments
+                in (1,1) double
+            end
+
+            global gVar1 gVar2
+            global pVar1 pVar2
+
+            try
+
+
+                switch in
+                  case 10
+                    disp('10');
+
+
+                    disp('11');
+                  otherwise
+
+                    disp('~10');
+
+                end
+            catch me
+
+                rethrow(me)
+            end
+
+            j = 0;
+            for n = 1:in
+                if mod(n, 5)
+                    x = 1;
+                    continue
+                elseif mod(n, 7)
+                    continue
+                else
+                    j = j + 1;
+                end
+                disp(['Divisible by 5 or 7 : ' num2str(n)])
+            end
+
+            x = 0
+            while x < 10
+                x = x + 1;
+            end
+        end
+
+        function method2()
+            n = 200;
+            A = 500;
+            a = zeros(1,n);
+            parfor i = 1:n
+                a(i) = max(abs(eig(rand(A))));
+            end
+
+            return
+        end
+    end
+
+    events
+        e1
+        e2
+    end
+
+    enumeration
+        one
+        two
+    end
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_matrix.m 
b/tests/test-matlab-ts-mode-indent-files/indent_matrix.m
new file mode 100644
index 0000000000..c0c4fbdea4
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_matrix.m
@@ -0,0 +1,33 @@
+% -*- matlab-ts -*-
+function a = indent_matrix
+    a = [ ...
+          1 ...
+          + ...
+          2
+        ];
+
+    a = [    2 ...
+             1 ...
+        ];
+
+    a = [ ...
+          2 + [ 3
+                4,
+                5 + [ ...
+                      2
+                    ]
+              ]
+        ];
+
+    a = [ ...
+          1; ...
+          2 ...
+        ];
+
+    long_variable_a = ...
+        [
+          2, 123, 456
+          3,   2    7
+        ];
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_matrix_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_matrix_expected.m
new file mode 100644
index 0000000000..c0c4fbdea4
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_matrix_expected.m
@@ -0,0 +1,33 @@
+% -*- matlab-ts -*-
+function a = indent_matrix
+    a = [ ...
+          1 ...
+          + ...
+          2
+        ];
+
+    a = [    2 ...
+             1 ...
+        ];
+
+    a = [ ...
+          2 + [ 3
+                4,
+                5 + [ ...
+                      2
+                    ]
+              ]
+        ];
+
+    a = [ ...
+          1; ...
+          2 ...
+        ];
+
+    long_variable_a = ...
+        [
+          2, 123, 456
+          3,   2    7
+        ];
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_nested.m 
b/tests/test-matlab-ts-mode-indent-files/indent_nested.m
new file mode 100644
index 0000000000..961815f5f7
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_nested.m
@@ -0,0 +1,29 @@
+% -*- matlab-ts -*-
+function b = indent_nested(a)
+
+    x = 1;
+
+    b = nested1(a);
+    
+    function d = nested1(c)
+    % help comment for
+    % nested1
+
+        % comment about next line, y =
+
+        y = 2;
+
+        d = nested2(c) + x;
+
+        function f = nested2(e)
+        % help comment for nested2
+
+            % comment about next line, f =
+            f = y + e * 3;
+        end
+    end
+
+    function nested3
+    end
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_nested_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_nested_expected.m
new file mode 100644
index 0000000000..d4ba72c314
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_nested_expected.m
@@ -0,0 +1,29 @@
+% -*- matlab-ts -*-
+function b = indent_nested(a)
+
+    x = 1;
+
+    b = nested1(a);
+
+    function d = nested1(c)
+    % help comment for
+    % nested1
+
+        % comment about next line, y =
+
+        y = 2;
+
+        d = nested2(c) + x;
+
+        function f = nested2(e)
+        % help comment for nested2
+
+            % comment about next line, f =
+            f = y + e * 3;
+        end
+    end
+
+    function nested3
+    end
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_old_indents.m 
b/tests/test-matlab-ts-mode-indent-files/indent_old_indents.m
new file mode 100644
index 0000000000..519e1f533a
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_old_indents.m
@@ -0,0 +1,375 @@
+% -*- matlab-ts -*-
+% See ../indents.m which is used in testing matlab-mode
+function indent_old_indents(a,b,stuff,cmddual1fake,cmddual2fake)
+% Help text
+% !!0
+% of many lines
+% !!0
+    
+    % including a gap - comment for following code
+    % !!4
+    
+    arguments (Repeating) % !!4
+        a (1,1) {mustBeNumeric}                                 % !!8
+        b (:,:) double                                          % !!8
+        stuff {mustBeMember(stuff, { 'this' 'that' 'other' })}  % !!8
+        cmddual1fake double  % !!8
+        cmddual2fake int     % !!8
+    end % !!4
+    
+    persistent var1 % !!4
+    global     var2 % !!4
+    persistent var3 % !!4
+    
+    
+    locala = a; %#ok
+    localb = b; %#ok
+    localstuff = stuff; %#ok
+    
+    if isempty(var1) var1=1; end %#ok !!4
+    if isempty(var3) var3=2; end %#ok !!4
+    
+    ends_in_comments_and_strings(var1, var2, var3); % !!4 has end in name
+    
+    % !!4
+    
+    block_starts_in_comments_and_strings(cmddual1fake,cmddual2fake);
+    array_constant_decls();
+    
+    % !!4
+    
+    continuations_and_block_comments();
+    
+    % $$$ !!0  
+    % $$$ special ignore comments
+    
+    has_nested_fcn(); % !!4
+    
+    % !!4  - after ignore comments
+    
+end % Comment with end in it
+
+%!!0
+
+function B = ends_in_comments_and_strings()
+% !!0
+    
+    % >>6
+    if foo
+        A = 1;
+    end % <<6 end in comment after end
+    
+    % !!4
+    
+    symbol_with_end_in_it;
+    
+    B = A(1:end); %#ok
+    
+    %% cell start comment !!4
+    if foo %!!4
+        C = "this is the end of the line";
+        % !!8
+    else %!!4
+        
+        % !!8
+    end;  D = "string end string";
+    % !!4
+    
+    E = [ D C];
+    
+    if bar
+        
+        A = E;
+        
+    end; B = A(1:end);
+    % !!4
+    
+    E = B;
+    
+    if baz
+        
+        A = C;
+        
+    end; B = [ 1 2 ...  % is this the end?
+               3 4 ];   % !!15
+    
+    % !!4
+    
+    if foo
+        
+        A = E;
+        
+    end ... the other end
+    % !! 4
+    
+    B = [ B A ]; % !!4
+    
+    str = 'This is a char array with ... in it';
+    foo(str); % !!4
+    
+    fcncall(arg1, '...', arg3); % !!4
+    1; % !!4
+
+    % Multi- end s
+    % >>8
+    if foo %#ok
+        if bar %#ok
+            if baz
+                
+                A = B;
+                
+            else
+                
+            end; end; end % <<8 comment end thing
+    
+    % !!4
+    B = A;
+    
+end
+
+function out = array_constant_decls()
+    
+    A = [ 1 2 3 ]; %!!4
+    
+    Blong = [ 1 2; %!!4
+              3 4; %!!14
+            ]; %!!12
+    
+    Csep = [ 
+             1 2; %!!8
+             3 4; %!!8
+           ]; %!!11
+    
+    multinest = { [ 1 2               %!!4
+                    3 4 ];            %!!20
+                  { 5 6 7 ...         %!!18
+                    8 9 10 ...        %!!20
+                  };                  %!!18
+                  fcncall(10, ...     %!!18
+                          12, ...     %!!26
+                          [ 13 14;    %!!26
+                            15 16 ])  %!!28
+                } ;  %!!16
+    
+    nest = { ... %!!4
+             1        %!!8
+             [ ...    %!!8
+               2 3    %!!10
+             ] ...    %!!8
+             3        %!!8
+           };    %!!11
+    
+    cascade_long_name = ... %!!4
+        { ...               %!!8
+          1                 %!!10
+          2                 %!!10
+        };                  %!!8
+    
+    % TODO
+    % I don't know why the below indents this way.
+    % It should either do all max indent, or all lined up with parens.
+    thing.thing.long.long.longname({ 'str' %!!4
+                                     'str' %!!37
+                                     'str' %!!37
+                                     'str' %!!37
+                                   });   %!!35
+    
+    thing.thing.long.long.longname('str', ... %!!4
+                                   'str', ... %!!35
+                                   'str', ... %!!35
+                                   'str' ...  %!!35
+                          );   %!!34
+    
+    % Line starting with end inside parens
+    disp(Csep(1:  ...  %!!4
+              end));   %!!14
+
+    % This array has bad syntactic expression parsing due to the
+    % apostrophy
+    Closures = [ 
+                 755009 ; ... % 21-Feb-2067 Washington's Birthday (Mon)
+                 755010 ;     % !!8
+               ];
+    
+    dep = [
+            root(info.function, factory, workspace, []), ...    % likewise 
this isn't a keyword
+            fcn3.finalize                                       % the single 
quote used to break [] scanning
+          ];
+    
+    % This long fcn name last symbol starts with 'get' which
+    % used to confuse and move to indent past 1st arg.
+    if qesimcheck.utils.GetYesNoAnswer('Do ',... !!4
+                                       'n',...  !!39
+                                       'once') %!!39
+        code();  %!!8
+    end  %!!4
+    
+    
+    
+    % !!4
+    out = { A     %!!4
+            Blong %!!12
+            Csep  %!!12
+            nest  %!!12
+            multinest%!!12
+            cascade_long_name%!!12
+            Closures%!!12
+            dep %!!12
+          };      %!!10
+
+end
+
+function C = block_starts_in_comments_and_strings(varargin)
+% !!0
+    
+    C = 0;
+    
+    if varargin{1} % if true
+        
+        % !!8
+    else % !!4
+
+        % !!8
+    end % if true
+    
+    
+    % see previous function
+    % !!4
+    for x=1:length(C) % !!4
+        if varargin{2}  % !!8
+            continue    % !!12
+        end   % !!8
+        
+        break % !!8
+              % !!14
+        
+        %!!8
+    end
+
+    switch foo()  %!!4
+      case 1  %!!6
+        
+        %!!8
+      otherwise %!!6
+        
+        %!!8
+    end %!!4
+
+    try
+        % !!8
+    catch %!!4    
+        
+        % !!8
+    end
+    
+end
+
+function B = continuations_and_block_comments
+% !!0
+% !!0
+% !!0
+%{
+  !!2  {  }
+  !!2
+%}
+    
+    %{
+      blank line means this block comment is not part of help and is for 
following code
+      !!6  {  }
+      !!6
+    %}
+
+    arg1=1;
+    
+    %{
+    %  !!4
+      !!6
+    % !!4
+    %}
+    
+    % Block comment indicators MUST be on a line by themselves.
+    %{ Not a block comment }
+    
+    foo(1); % !!4   - don't indent this special
+    
+    %} Not an end to a block comment {
+    
+    foo(arg1, ... %!!4
+        arg2);  %!!8
+    
+    foo_long_fcn(arg1, ... %!!4
+                 arg2); %!!17
+    
+    A = [ 1 2  % !!4
+          3 4 ]; % !!10
+    
+    foo(['this is a very long string', ... %!!4
+         'with a continution to do something very exciting']);%!!9
+    
+    set(gcf,'Position',[ 1 2 3 4], ... !!4
+            'Color', 'red');  % !!12
+    
+    B = A + 1 + 4 ...
+        + 6; % !!8
+    
+    foo_code();  % eol-comment !!4
+                 % continuation-comment !!17
+    
+    % !!4 -blank between this & continuation comment
+    % !!4 - more comments
+
+    if condition1 || ...  % !!4
+       fcn_call(arg1, ... % !!12
+                arg2)  % !!21
+        line_in_if();
+    end  % !!4
+    
+    
+    
+end
+
+function has_nested_fcn
+
+    plot(1:10); %!!4
+    
+    A = 1;
+    
+    function am_nested_fcn() %!!4
+                             % help
+                             % !!4
+        code(A);
+    %!!8
+    end
+    
+    %!!4
+    am_nested_fcn();
+    function_end_same_line(1);
+    function_after_end_same_line();
+end
+
+function b=function_end_same_line(a), b=a; end %!!0
+
+function function_after_end_same_line()%!!0
+                                       %!!0
+    disp('foo');%!!4
+    
+    debug_cmd_dual();
+    
+end%!!0
+
+function debug_cmd_dual ()
+% These dbstop command dual content have 'if' blocks in them.
+% The command dual detection needs to block these from being
+% detected as block initiators which would cause indentaiton.
+    
+    dbstop in hRandomFile at 14 if func() % !!4
+    dbstop in hRandomFile at 30@1 if x==1 % !!4
+    dbstop in hPFile                      % !!4
+    dbstop in hSimpleFile at 2            % !!4
+    dbstop if error                       % !!4
+    
+    %!!4
+
+    debug_cmd_dual(); %!!4
+    
+end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_old_indents_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_old_indents_expected.m
new file mode 100644
index 0000000000..3abeed5292
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_old_indents_expected.m
@@ -0,0 +1,375 @@
+% -*- matlab-ts -*-
+% See ../indents.m which is used in testing matlab-mode
+function indent_old_indents(a,b,stuff,cmddual1fake,cmddual2fake)
+% Help text
+% !!0
+% of many lines
+% !!0
+
+    % including a gap - comment for following code
+    % !!4
+
+    arguments (Repeating) % !!4
+        a (1,1) {mustBeNumeric}                                 % !!8
+        b (:,:) double                                          % !!8
+        stuff {mustBeMember(stuff, { 'this' 'that' 'other' })}  % !!8
+        cmddual1fake double  % !!8
+        cmddual2fake int     % !!8
+    end % !!4
+
+    persistent var1 % !!4
+    global     var2 % !!4
+    persistent var3 % !!4
+
+
+    locala = a; %#ok
+    localb = b; %#ok
+    localstuff = stuff; %#ok
+
+    if isempty(var1) var1=1; end %#ok !!4
+    if isempty(var3) var3=2; end %#ok !!4
+
+    ends_in_comments_and_strings(var1, var2, var3); % !!4 has end in name
+
+    % !!4
+
+    block_starts_in_comments_and_strings(cmddual1fake,cmddual2fake);
+    array_constant_decls();
+
+    % !!4
+
+    continuations_and_block_comments();
+
+    % $$$ !!0
+    % $$$ special ignore comments
+
+    has_nested_fcn(); % !!4
+
+    % !!4  - after ignore comments
+
+end % Comment with end in it
+
+%!!0
+
+function B = ends_in_comments_and_strings()
+% !!0
+
+    % >>6
+    if foo
+        A = 1;
+    end % <<6 end in comment after end
+
+    % !!4
+
+    symbol_with_end_in_it;
+
+    B = A(1:end); %#ok
+
+    %% cell start comment !!4
+    if foo %!!4
+        C = "this is the end of the line";
+        % !!8
+    else %!!4
+
+        % !!8
+    end;  D = "string end string";
+    % !!4
+
+    E = [ D C];
+
+    if bar
+
+        A = E;
+
+    end; B = A(1:end);
+    % !!4
+
+    E = B;
+
+    if baz
+
+        A = C;
+
+    end; B = [ 1 2 ...  % is this the end?
+               3 4 ];   % !!15
+
+    % !!4
+
+    if foo
+
+        A = E;
+
+    end ... the other end
+    % !! 4
+
+    B = [ B A ]; % !!4
+
+    str = 'This is a char array with ... in it';
+    foo(str); % !!4
+
+    fcncall(arg1, '...', arg3); % !!4
+    1; % !!4
+
+    % Multi- end s
+    % >>8
+    if foo %#ok
+        if bar %#ok
+            if baz
+
+                A = B;
+
+            else
+
+            end; end; end % <<8 comment end thing
+
+    % !!4
+    B = A;
+
+end
+
+function out = array_constant_decls()
+
+    A = [ 1 2 3 ]; %!!4
+
+    Blong = [ 1 2; %!!4
+              3 4; %!!14
+            ]; %!!12
+
+    Csep = [
+             1 2; %!!8
+             3 4; %!!8
+           ]; %!!11
+
+    multinest = { [ 1 2               %!!4
+                    3 4 ];            %!!20
+                  { 5 6 7 ...         %!!18
+                    8 9 10 ...        %!!20
+                  };                  %!!18
+                  fcncall(10, ...     %!!18
+                          12, ...     %!!26
+                          [ 13 14;    %!!26
+                            15 16 ])  %!!28
+                } ;  %!!16
+
+    nest = { ... %!!4
+             1        %!!8
+             [ ...    %!!8
+               2 3    %!!10
+             ] ...    %!!8
+             3        %!!8
+           };    %!!11
+
+    cascade_long_name = ... %!!4
+        { ...               %!!8
+          1                 %!!10
+          2                 %!!10
+        };                  %!!8
+
+    % TODO
+    % I don't know why the below indents this way.
+    % It should either do all max indent, or all lined up with parens.
+    thing.thing.long.long.longname({ 'str' %!!4
+                                     'str' %!!37
+                                     'str' %!!37
+                                     'str' %!!37
+                                   });   %!!35
+
+    thing.thing.long.long.longname('str', ... %!!4
+                                   'str', ... %!!35
+                                   'str', ... %!!35
+                                   'str' ...  %!!35
+                          );   %!!34
+
+    % Line starting with end inside parens
+    disp(Csep(1:  ...  %!!4
+              end));   %!!14
+
+    % This array has bad syntactic expression parsing due to the
+    % apostrophy
+    Closures = [
+                 755009 ; ... % 21-Feb-2067 Washington's Birthday (Mon)
+                 755010 ;     % !!8
+               ];
+
+    dep = [
+            root(info.function, factory, workspace, []), ...    % likewise 
this isn't a keyword
+            fcn3.finalize                                       % the single 
quote used to break [] scanning
+          ];
+
+    % This long fcn name last symbol starts with 'get' which
+    % used to confuse and move to indent past 1st arg.
+    if qesimcheck.utils.GetYesNoAnswer('Do ',... !!4
+                                       'n',...  !!39
+                                       'once') %!!39
+        code();  %!!8
+    end  %!!4
+
+
+
+    % !!4
+    out = { A     %!!4
+            Blong %!!12
+            Csep  %!!12
+            nest  %!!12
+            multinest%!!12
+            cascade_long_name%!!12
+            Closures%!!12
+            dep %!!12
+          };      %!!10
+
+end
+
+function C = block_starts_in_comments_and_strings(varargin)
+% !!0
+
+    C = 0;
+
+    if varargin{1} % if true
+
+        % !!8
+    else % !!4
+
+        % !!8
+    end % if true
+
+
+    % see previous function
+    % !!4
+    for x=1:length(C) % !!4
+        if varargin{2}  % !!8
+            continue    % !!12
+        end   % !!8
+
+        break % !!8
+              % !!14
+
+        %!!8
+    end
+
+    switch foo()  %!!4
+      case 1  %!!6
+
+        %!!8
+      otherwise %!!6
+
+        %!!8
+    end %!!4
+
+    try
+        % !!8
+    catch %!!4
+
+        % !!8
+    end
+
+end
+
+function B = continuations_and_block_comments
+% !!0
+% !!0
+% !!0
+%{
+  !!2  {  }
+  !!2
+%}
+
+    %{
+      blank line means this block comment is not part of help and is for 
following code
+      !!6  {  }
+      !!6
+    %}
+
+    arg1=1;
+
+    %{
+    %  !!4
+      !!6
+    % !!4
+    %}
+
+    % Block comment indicators MUST be on a line by themselves.
+    %{ Not a block comment }
+
+    foo(1); % !!4   - don't indent this special
+
+    %} Not an end to a block comment {
+
+    foo(arg1, ... %!!4
+        arg2);  %!!8
+
+    foo_long_fcn(arg1, ... %!!4
+                 arg2); %!!17
+
+    A = [ 1 2  % !!4
+          3 4 ]; % !!10
+
+    foo(['this is a very long string', ... %!!4
+         'with a continution to do something very exciting']);%!!9
+
+    set(gcf,'Position',[ 1 2 3 4], ... !!4
+        'Color', 'red');  % !!12
+
+    B = A + 1 + 4 ...
+        + 6; % !!8
+
+    foo_code();  % eol-comment !!4
+                 % continuation-comment !!17
+
+    % !!4 -blank between this & continuation comment
+    % !!4 - more comments
+
+    if condition1 || ...  % !!4
+       fcn_call(arg1, ... % !!12
+                arg2)  % !!21
+        line_in_if();
+    end  % !!4
+
+
+
+end
+
+function has_nested_fcn
+
+    plot(1:10); %!!4
+
+    A = 1;
+
+    function am_nested_fcn() %!!4
+                             % help
+                             % !!4
+        code(A);
+    %!!8
+    end
+
+    %!!4
+    am_nested_fcn();
+    function_end_same_line(1);
+    function_after_end_same_line();
+end
+
+function b=function_end_same_line(a), b=a; end %!!0
+
+function function_after_end_same_line()%!!0
+                                       %!!0
+    disp('foo');%!!4
+
+    debug_cmd_dual();
+
+end%!!0
+
+function debug_cmd_dual ()
+% These dbstop command dual content have 'if' blocks in them.
+% The command dual detection needs to block these from being
+% detected as block initiators which would cause indentaiton.
+
+    dbstop in hRandomFile at 14 if func() % !!4
+    dbstop in hRandomFile at 30@1 if x==1 % !!4
+    dbstop in hPFile                      % !!4
+    dbstop in hSimpleFile at 2            % !!4
+    dbstop if error                       % !!4
+
+    %!!4
+
+    debug_cmd_dual(); %!!4
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_ranges.m 
b/tests/test-matlab-ts-mode-indent-files/indent_ranges.m
new file mode 100644
index 0000000000..a7d9de26cf
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_ranges.m
@@ -0,0 +1,15 @@
+% -*- matlab-ts -*-
+
+myMatrix = [ 
+             1 2;
+             3 4;
+           ];
+
+disp(myMatrix(1:  ...
+              end)); 
+
+disp(myMatrix(1: (1 + ...
+                  2)));
+
+disp(myMatrix(1: [1 + ...
+                  2]));
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_ranges_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_ranges_expected.m
new file mode 100644
index 0000000000..79a13962c7
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_ranges_expected.m
@@ -0,0 +1,15 @@
+% -*- matlab-ts -*-
+
+myMatrix = [
+             1 2;
+             3 4;
+           ];
+
+disp(myMatrix(1:  ...
+              end));
+
+disp(myMatrix(1: (1 + ...
+                  2)));
+
+disp(myMatrix(1: [1 + ...
+                  2]));
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_switch.m 
b/tests/test-matlab-ts-mode-indent-files/indent_switch.m
new file mode 100644
index 0000000000..124d20aa0e
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_switch.m
@@ -0,0 +1,25 @@
+% -*- matlab-ts -*-
+function indent_switch(in)
+
+
+    switch in
+
+
+      case 10
+
+        disp('one')
+
+
+        disp('two');
+
+
+        a = 1
+
+      otherwise
+
+        disp('foo')
+
+
+    end
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_switch_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_switch_expected.m
new file mode 100644
index 0000000000..124d20aa0e
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_switch_expected.m
@@ -0,0 +1,25 @@
+% -*- matlab-ts -*-
+function indent_switch(in)
+
+
+    switch in
+
+
+      case 10
+
+        disp('one')
+
+
+        disp('two');
+
+
+        a = 1
+
+      otherwise
+
+        disp('foo')
+
+
+    end
+
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_tab_between_fcns.m 
b/tests/test-matlab-ts-mode-indent-files/indent_tab_between_fcns.m
new file mode 100644
index 0000000000..8c4c836c70
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_tab_between_fcns.m
@@ -0,0 +1,6 @@
+% -*- matlab-ts -*-
+function indent_tab_between_fcns
+end
+
+function b
+end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_tab_between_fcns_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_tab_between_fcns_expected.m
new file mode 100644
index 0000000000..8c4c836c70
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_tab_between_fcns_expected.m
@@ -0,0 +1,6 @@
+% -*- matlab-ts -*-
+function indent_tab_between_fcns
+end
+
+function b
+end
diff --git a/tests/test-matlab-ts-mode-indent-files/indent_tab_in_fcn.m 
b/tests/test-matlab-ts-mode-indent-files/indent_tab_in_fcn.m
new file mode 100644
index 0000000000..36854dc570
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_tab_in_fcn.m
@@ -0,0 +1,5 @@
+% -*- matlab-ts -*-
+function indent_tab_in_fcn
+    disp('here')
+
+end
diff --git 
a/tests/test-matlab-ts-mode-indent-files/indent_tab_in_fcn_expected.m 
b/tests/test-matlab-ts-mode-indent-files/indent_tab_in_fcn_expected.m
new file mode 100644
index 0000000000..36854dc570
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent-files/indent_tab_in_fcn_expected.m
@@ -0,0 +1,5 @@
+% -*- matlab-ts -*-
+function indent_tab_in_fcn
+    disp('here')
+
+end
diff --git a/tests/test-matlab-ts-mode-indent.el 
b/tests/test-matlab-ts-mode-indent.el
new file mode 100644
index 0000000000..60e87a631e
--- /dev/null
+++ b/tests/test-matlab-ts-mode-indent.el
@@ -0,0 +1,166 @@
+;;; test-matlab-ts-mode-indent.el --- Test matlab-ts-mode indent -*- 
lexical-binding: t -*-
+;;
+;; 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, 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 GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;;
+
+;;; Commentary:
+;;
+;; Validate matlab-ts-mode indent.
+;; Load ../matlab-ts-mode.el via require and run indent tests using
+;; ./test-matlab-ts-mode-indent-files/NAME.m comparing against
+;; ./test-matlab-ts-mode-indent-files/NAME_expected.m
+;;
+
+;;; Code:
+
+(require 'cl-seq)
+
+;; Add abs-path of ".." to load-path so we can (require 'matlab-ts-mode)
+(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 'matlab-ts-mode)
+
+(setq matlab-ts-mode--indent-assert t)
+
+(defun test-matlab-ts-mode-indent-files ()
+  "Return list of full paths to each test-matlab-ts-mode-indent-files/*.m."
+  (cl-delete-if (lambda (m-file)
+                  (string-match "_expected\\.m$" m-file))
+                (directory-files "test-matlab-ts-mode-indent-files" t 
"\\.m$")))
+
+(defvar test-matlab-ts-mode-indent (cons "test-matlab-ts-mode-indent"
+                                         (test-matlab-ts-mode-indent-files)))
+
+(defun trim ()
+  "Trim trailing whitespace and lines."
+  (setq buffer-file-coding-system 'utf-8-unix)
+  (let ((delete-trailing-lines t))
+    (delete-trailing-whitespace (point-min) (point-max))))
+
+(defun test-matlab-ts-mode-indent--typing (m-file expected expected-file)
+  "Exercise indent by simulating the creation of M-FILE via typing.
+This compares the simulation of typing M-FILE line by line against
+EXPECTED content in EXPECTED-FILE."
+
+  (message "START: test-matlab-ts-mode-indent (typing) %s" m-file)
+
+  (let* ((typing-m-file-name (concat "typing__" (file-name-nondirectory 
m-file)))
+         (contents (with-temp-buffer
+                     (insert-file-contents-literally m-file)
+                     (buffer-substring (point-min) (point-max))))
+         (lines (split-string (string-trim contents) "\n")))
+    (with-current-buffer (get-buffer-create typing-m-file-name)
+      (erase-buffer)
+      (matlab-ts-mode)
+
+      ;; Insert the non-empty lines into typing-m-file-name buffer
+      (dolist (line lines)
+        (setq line (string-trim line))
+        (when (not (string= line ""))
+          (insert line "\n")))
+
+      ;; Now indent each line and insert the empty ("") lines into 
typing-m-file-buffer
+      ;; as we indent. This exercises the RET and TAB behaviors which cause 
different
+      ;; tree-sitter nodes to be provided to the indent engine rules.
+      (goto-char (point-min))
+      (while (not (eobp))
+
+        ;; Workaround 
https://github.com/acristoffers/tree-sitter-matlab/issues/32
+       (let* ((node   (treesit-node-at (point)))
+              (parent (and node (treesit-node-parent node))))
+         (when (string= (treesit-node-type parent) "ERROR")
+           (insert " ")))
+               
+        (call-interactively #'indent-for-tab-command) ;; TAB on code just added
+
+        ;; While next line in our original contents is a newline insert "\n"
+        (while (let ((next-line (nth (line-number-at-pos (point)) lines)))
+                 (and next-line (string-match-p "^[ \t\r]*$" next-line)))
+          (goto-char (line-end-position))
+          ;; RET to add blank line
+          (call-interactively #'newline)
+          ;; TAB on the same blank line can result in different tree-sitter 
nodes than
+          ;; the RET, so exercise that.
+          (call-interactively #'indent-for-tab-command))
+        (forward-line))
+
+      (trim)
+
+      (let ((typing-got (buffer-substring (point-min) (point-max))))
+        (set-buffer-modified-p nil)
+        (kill-buffer)
+        (when (not (string= typing-got expected))
+          (let ((coding-system-for-write 'raw-text-unix)
+                (typing-got-file (replace-regexp-in-string "\\.m$" 
"_typing.m~" m-file)))
+            (write-region typing-got nil typing-got-file)
+            (error "Typing %s line-by-line does not match %s, we got %s" 
m-file expected-file
+                   typing-got-file)))))))
+
+(defun test-matlab-ts-mode-indent (&optional m-file)
+  "Test indent using ./test-matlab-ts-mode-indent-files/NAME.m.
+Compare indent of ./test-matlab-ts-mode-indent-files/NAME.m against
+./test-matlab-ts-mode-indent-files/NAME_expected.m
+
+If M-FILE (NAME.m) is not provided, loop comparing all
+./test-matlab-ts-mode-indent-files/NAME.m files.
+
+For debugging, you can run with a specified NAME.m,
+  M-: (test-matlab-ts-mode-font-lock 
\"test-matlab-ts-mode-indent-files/NAME.m\")"
+  
+  (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-indent-files))))
+    (dolist (m-file m-files)
+      (let* ((expected-file (replace-regexp-in-string "\\.m$" "_expected.m" 
m-file))
+             (expected (when (file-exists-p expected-file)
+                         (with-temp-buffer
+                           (insert-file-contents-literally expected-file)
+                           (buffer-string)))))
+
+        (save-excursion
+          (message "START: test-matlab-ts-mode-indent %s" m-file)
+          (find-file m-file)
+          (indent-region (point-min) (point-max))
+          (trim)
+          (let ((got (buffer-substring (point-min) (point-max)))
+                (got-file (concat expected-file "~")))
+            (set-buffer-modified-p nil)
+            (kill-buffer)
+            (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 - if %s looks good 
rename it to %s"
+                       m-file got-file expected-file))
+              (error "Baseline for %s does not match, got: %s, expected: %s"
+                     m-file got-file expected-file))))
+
+        (when expected ;; expected-file exists?
+          (test-matlab-ts-mode-indent--typing m-file expected expected-file)))
+          
+      (message "PASS: test-matlab-ts-mode-indent %s" m-file)))
+  "success")
+
+(provide 'test-matlab-ts-mode-indent)
+;;; test-matlab-ts-mode-indent.el ends here

Reply via email to