branch: externals/matlab-mode
commit 075a3b1d64d5122c514d03b787c1462e0fcc52a7
Author: John Ciolfi <[email protected]>
Commit: John Ciolfi <[email protected]>
matlab-ts-mode: place electric indent code in matlab-ts-mode--ei.el
---
matlab-ts-mode--ei.el | 1028 ++++++++++++++++++++++++++++++++++++++++++++++
matlab-ts-mode.el | 1078 ++-----------------------------------------------
2 files changed, 1068 insertions(+), 1038 deletions(-)
diff --git a/matlab-ts-mode--ei.el b/matlab-ts-mode--ei.el
new file mode 100644
index 0000000000..35ec7d2007
--- /dev/null
+++ b/matlab-ts-mode--ei.el
@@ -0,0 +1,1028 @@
+;;; matlab-ts-mode--ei.el --- MATLAB electric indent -*- lexical-binding: t -*-
+
+;; Version: 8.0.0
+;; URL: https://github.com/mathworks/Emacs-MATLAB-Mode
+;; SPDX-License-Identifier: GPL-3.0-or-later
+;;
+;; Author: John Ciolfi <[email protected]>
+;; Created: Jul-7-2025
+;; Keywords: MATLAB
+
+;; Copyright (C) 2025-2026 Free Software Foundation, Inc.
+;;
+;; 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:
+;;
+;; Electric indent for matlab-ts-mode
+
+;;; Code:
+
+(require 'treesit)
+
+(defvar matlab-ts-mode--array-indent-level)
+(defvar matlab-ts-mode--indent-level)
+(declare-function matlab-ts-mode "matlab-ts-mode.el")
+
+(defgroup matlab-ts nil
+ "MATLAB(R) tree-sitter mode."
+ :prefix "matlab-ts-mode-"
+ :group 'languages)
+
+(defcustom matlab-ts-mode-electric-indent t
+ "*If t, indent (format) language elements within code.
+- Canonicalize language elements spacing
+ Example | Result
+ ----------------------------- | -----------------------------
+ a = b+ c *d ; | a = b + c * d;
+
+- Align consecutive assignments
+ Example | Result
+ ----------------------------- | -----------------------------
+ width = 100; | width = 100;
+ length = 200; | length = 200;
+ area=width * length; | area = width * length;
+
+- Align trailing comments
+ Example | Result
+ ----------------------------- | -----------------------------
+ a = myFcn1(1, 2); % comment 1 | a = myFcn1(1, 2); % comment 1
+ a = a + 5; % comment 2 | a = a + 2; % comment 2
+
+- Align matrix columns
+ Example | Result
+ ----------------------------- | -----------------------------
+ m = [2,4000 | m = [ 2, 4000
+ 3000,1] | 3000, 1]
+
+- Align properties
+ Example:
+ TODO
+ is indented as:
+ TODO"
+ :type 'boolean)
+
+(defvar matlab-ts-mode--electric-indent-verbose nil)
+(defvar matlab-ts-mode--electric-indent-assert nil)
+
+(defvar matlab-ts-mode--ei-keywords-re
+ (rx bos (or "arguments" ;; not technically a keyword (can be a variable),
but treat as a keyword
+ "break"
+ "case"
+ "catch"
+ "classdef"
+ "continue"
+ "else"
+ "elseif"
+ "end"
+ "enumeration"
+ "events"
+ "for"
+ "function"
+ "get." ;; when used in a classdef method, e.g. function value =
get.propName(obj)
+ "global"
+ "if"
+ "methods"
+ "otherwise"
+ "parfor"
+ "persistent"
+ "properties"
+ "return"
+ "set." ;; when used in a classdef method, e.g. function obj =
set.propName(obj, val)
+ "spmd"
+ "switch"
+ "try"
+ "while")
+ eos))
+
+(defvar matlab-ts-mode--ei-pad-op-re
+ (rx bos (or "+" "-" "*" "/" ".*" "./" "\\"
+ "==" "~=" ">" ">=" "<" "<="
+ "&" "|" "&&" "||"
+ "="
+ "^" ".^"
+ "'" ".'"
+ ":")
+ eos))
+
+(defvar matlab-ts-mode--ei-0-after-re
+ (rx bos (or "[" "{" "(" "~" "unary-op") eos))
+
+(defvar matlab-ts-mode--ei-val-re (rx bos (or "identifier" "number") eos))
+
+;; TODO optimize following by grouping together, also improve comments
+(defvar matlab-ts-mode--ei-spacing
+ ;; In a given line, we walk across the nodes adjusting spaces between NODE
and NEXT-NODE to
+ ;; have N-SPACES-BETWEEN them.
+ ;;
+ ;; NODE-RE NEXT-NODE-RE
N-SPACES-BETWEEN
+ `(
+
+ ("." ,(rx bos (or "comment"
"line_continuation") eos) 1)
+
+
+ ;; Case: property dimension
+ ;; foo1 (1, :) {mustBeNumeric, mustBeReal} = [0, 0, 0];
+ ("." ,(rx bos "prop-dim" eos)
0)
+
+ ;; Case; [email protected];
+ ;; TopTester:
tests/test-matlab-ts-mode-electric-indent-files/electric_indent_call_super.m
+ ("." ,(rx bos "@-fcn-call" eos)
0)
+ (,(rx bos "@-fcn-call" eos) "."
0)
+
+ ;; Case: a.?b, M', M.'
+ ("." ,(rx bos (or ".?" "'" ".'") eos)
0)
+ (,(rx bos ".?" eos) "."
0)
+
+ ;; Case: power and transpose: a^b a.^b
+ (,matlab-ts-mode--ei-val-re (,(rx bos (or "^" ".^") eos) .
,matlab-ts-mode--ei-val-re) 0)
+
+ ;; Case: anything followed by ",", etc.: [a, b]
+ ("." ,(rx bos (or "," ";" ".") eos)
0)
+
+ ;; Case lambda: @(x), metaclass operator, ?
+ (,(rx bos (or "@" "?") eos) "."
0)
+
+ ;; Case: anything after a ".", e.g. foo.bar, s.(fieldName)
+ (,(rx bos "." eos) "."
0)
+
+ (,(rx bos (or "," ";" "command_argument" "command_name") eos) "."
1)
+
+ (,matlab-ts-mode--ei-0-after-re "."
0)
+
+ (,(rx bos "]" eos) ,(rx bos (or "," ";") eos)
0)
+ (,(rx bos "]" eos) ,(rx bos "[" eos)
1)
+ ("." ,(rx bos (or "]" ")" "}") eos)
0)
+
+ ;; Case: ") identifier" as in: propName (1, 1) double
+ ;; arguments: g (1,1) {mustBeNumeric, mustBeReal}
+ ;; @(x) ((ischar(x) || isstring(x)));
+ (,(rx bos ")" eos) ,(rx bos (or "identifier" "{" "(") eos)
1)
+
+ ;; Case: property identifier: propName (1,1) double
+ (,(rx bos "property-id" eos) "."
1)
+
+ ;; Case: padded operators, e.g.: a || b
+ (,matlab-ts-mode--ei-pad-op-re "."
1)
+ ("." ,matlab-ts-mode--ei-pad-op-re
1)
+
+ ;; Case: string followed by anything, e.g. ["string1" foo(1)]
+ (,(rx bos "string" eos) "."
1)
+
+ ;; Case: anything before string, e.g. [foo(1) "string1"]
+ ("." ,(rx bos "string" eos)
1)
+
+ ;; Case: c3 = {b [c '%']};
+ ("." ,(rx bos "[" eos)
1)
+
+ (,(rx bos "identifier" eos) ,(rx bos (or "(" "{") eos)
0)
+ (,(rx bos "identifier" eos) "."
1)
+
+ ;; Case: number in matrix: [123 456]
+ (,(rx bos "number" eos) "."
1)
+
+ ;; Case: subclass
+ (,(rx bos "<" eos) "."
1)
+
+ ;; Case: keywords, e.g. if condition
+ (,matlab-ts-mode--ei-keywords-re "."
1)
+
+ ;; Case: c = {['freq' '%'] num2str(2)};
+ (,(rx bos "]" eos) "."
1)
+
+ ;; Case: c4{1} = [1 2; 3 4];
+ ;; v4 = [c4{1}(1,1), c4{1}(1,1)];
+ (,(rx bos "}" eos) ,(rx bos "(" eos)
0)
+
+ ;; Case: ")": m3 = uint8([ones(20,1); 2*ones(8,1)]);
+ ;; ^ ^
+ ;; Case: c3 = {{[17.50 0] [17.50 0]} {[120 0] [120 20]}};
+ ;; ^ ^
+ (,(rx bos (or ")" "}") eos) "."
1)
+ ))
+
+(cl-defun matlab-ts-mode--ei-move-to-and-get-node ()
+ "Move to and return node.
+Will return nil if no next node before end-of-line.
+Assumes point is at of current node or beginning of line."
+ ;; Move point to first non-whitespace char
+ (let ((eol (line-end-position)))
+ (when (looking-at "[ \t]")
+ (when (not (re-search-forward "[^ \t]" eol t))
+ (cl-return-from matlab-ts-mode--ei-move-to-and-get-node))
+ (backward-char))
+
+ (let ((node (treesit-node-at (point)))
+ node-type)
+
+ ;; Consider [[1,2];[3,4]] when point is on semicolon, node will be the
prior "]" because the
+ ;; semicolon is an ignored node, so move forward to get to the "[" after
the semicolon.
+ (while (and (not (eobp))
+ (let ((node-start (treesit-node-start node)))
+ (and (>= node-start (line-beginning-position))
+ (< node-start (point)))))
+ (forward-char)
+ (setq node (treesit-node-at (point))))
+
+ ;; Don't go past end-of-line point
+ (when (or (equal "\n" (treesit-node-type node))
+ (> (treesit-node-start node) eol)
+ ;; When we get to EOL and in error context, node start will be
on an earlier line
+ ;; x = [
+ ;; TAB> 1 , 2 ;
+ (< (treesit-node-start node) (line-beginning-position)))
+ (goto-char eol)
+ (setq node nil))
+
+ (when node
+ (setq node-type (treesit-node-type node))
+ (let* ((parent (treesit-node-parent node))
+ (parent-type (treesit-node-type parent)))
+ (cond
+ ;; Use string and not the elements of the string
+ ((equal parent-type "string")
+ (setq node parent
+ node-type parent-type))
+
+ ;; convert property identifier to property-id node-type
+ ((and (equal node-type "identifier")
+ (or
+ ;; propertyWithOutDot?
+ (and (equal parent-type "property")
+ (equal (treesit-node-child parent 0) node))
+ ;; property.nameWithDot?
+ (and (equal parent-type "property_name")
+ (equal (treesit-node-child (treesit-node-parent parent)
0) parent))))
+ (setq node-type "property-id"))
+
+ ;; Unary operator sign, + or -, e.g. [0 -e] or g = - e
+ ((and (equal parent-type "unary_operator")
+ (equal (treesit-node-child parent 0) node))
+ (setq node-type "unary-op"))
+
+ ;; Super-class constructor call
+ ;; [email protected];
+ ((and (equal node-type "@")
+ (equal parent-type "function_call"))
+ (setq node-type "@-fcn-call"))
+
+ ;; Property dimensions
+ ;; foo1 (1, :) {mustBeNumeric, mustBeReal} = [0, 0, 0];
+ ((and (or (equal node-type "number") (equal node-type ":"))
+ (or (equal parent-type "dimensions")
+ (and (equal parent-type "spread_operator")
+ (equal (treesit-node-type (treesit-node-parent
parent))
+ "dimensions"))))
+ (setq node-type "prop-dim"))
+ )))
+ (cons node node-type))))
+
+(defun matlab-ts-mode--ei-assert-match (line-node-types)
+ "Assert that LINE-NODE-TYPES string matches current line."
+ (back-to-indentation)
+ (let (curr-line-node-types)
+ (cl-loop
+ while (< (point) (line-end-position))
+ do
+ (let* ((pair (matlab-ts-mode--ei-move-to-and-get-node))
+ (node (or (car pair)
+ (cl-return)))
+ (node-type (cdr pair)))
+ (setq curr-line-node-types
+ (matlab-ts-mode--ei-update-line-node-types curr-line-node-types
+ node node-type))
+ (let ((node-end (treesit-node-end node)))
+ (if (< node-end (line-end-position))
+ (goto-char node-end)
+ (goto-char (line-end-position))))))
+
+ (when (not (string= curr-line-node-types line-node-types))
+ (error "Assert: line-node-types mismatch \"%s\" !EQ \"%s\" at line %d in
%s"
+ curr-line-node-types line-node-types (line-number-at-pos (point))
(buffer-name)))))
+
+(defun matlab-ts-mode--ei-concat-line (ei-line node extra-chars &optional
n-spaces-to-append)
+ "Return concat EI-LINE with NODE text.
+NODE-END is the NODE end accounting for ignored nodes (semicolons).
+EXTRA-CHARS are appended to EL-LINE.
+N-SPACES-TO-APPEND is the number of spaces to append between nodes."
+
+ (let* ((node-end (treesit-node-end node))
+ (last-pt (if (< node-end (line-end-position)) node-end
(line-end-position))))
+ (concat ei-line
+ (buffer-substring (treesit-node-start node) last-pt)
+ extra-chars
+ (if (not n-spaces-to-append) ;; last node?
+ ;; Add trailing whitespace when in an ERROR node. Consider
+ ;; switch a
+ ;; case ;; One trailing whitespace
+ ;; end
+ ;; TopTester: electric_indent_xr_switch.m
+ (when (and (treesit-parent-until node (rx bos "ERROR" eos))
+ (< last-pt (line-end-position)))
+ (save-excursion
+ (end-of-line)
+ (when (re-search-backward "[^ \t]"
(line-beginning-position) t)
+ (forward-char)
+ (when (not (= (point) (line-end-position)))
+ (buffer-substring (point) (line-end-position))
+ ))))
+ (when (> n-spaces-to-append 0)
+ (make-string n-spaces-to-append ? ))))))
+
+(defun matlab-ts-mode--ei-no-elements-to-indent ()
+ "Return t if no elements in the current line to indent.
+Assumes that current point is at `back-to-indentation'."
+ (or
+ ;; (1) Empty line?
+ (not (looking-at "[^ \t\n\r]"))
+ ;; (2) Comment line? Nothing to indent in line if it's a comment line.
+ (let ((first-node-type (or (treesit-node-type (treesit-node-at (point)))
"")))
+ (string-match-p (rx bos (or "line_continuation" "comment") eos)
first-node-type))
+ ;; (3) Syntax error *within* the line? If error node covers whole line,
assume nodes in
+ ;; line are good, i.e. electric indent the line.
+ (let ((beg (line-beginning-position))
+ (end (line-end-position))
+ (capture-errors (treesit-query-capture (treesit-buffer-root-node)
'((ERROR) @e))))
+ (cl-loop
+ for capture-error in capture-errors do
+ (let* ((error-node (cdr capture-error))
+ (error-start (treesit-node-start error-node))
+ (error-end (treesit-node-end error-node)))
+ (when (and (> error-start beg)
+ (< error-end end))
+ (cl-return t))
+ )))))
+
+(defun matlab-ts-mode--ei-node-extra-chars (node-end next-node-start)
+ "Get extra chars after NODE-END and before NEXT-NODE-START."
+
+ (let ((extra-chars ""))
+ ;; Handle ignored characters, e.g. ";" in matrices where node="]",
next-node="["
+ ;; [[1, 2]; [3, 4]]
+ ;; ^ ^
+ (goto-char node-end)
+ (when (or ;; [[1,2];[3,4]]?
+ (and (< node-end next-node-start)
+ (looking-at "[^ \t]"))
+ ;; [[1,2] ; [3,4]]?
+ ;; or a multiline matrix:
+ ;; x = [ 1 , 2 ;
+ (save-excursion (and (when (re-search-forward "[^ \t]"
next-node-start t)
+ (backward-char)
+ t)
+ (< (point) next-node-start))))
+ (while (< (point) next-node-start)
+ (while (and (< (point) next-node-start)
+ (looking-at "[^ \t]"))
+ (setq extra-chars (concat extra-chars (match-string 0)))
+ (forward-char)
+ (setq node-end (point)))
+ (re-search-forward "[ \t]+" next-node-start t)))
+ extra-chars))
+
+(defun matlab-ts-mode--ei-update-line-node-types (line-node-types node
node-type)
+ "Append NODE-TYPE of NODE to LINE-NODE-TYPES."
+ (if (and (string= node-type ";")
+ (string= (treesit-node-type (treesit-node-parent node)) "matrix"))
+ ;; Ignore ';' row separator in matrix because these may be ignored by
tree-sitter
+ line-node-types
+ (concat line-node-types (when line-node-types " ") node-type)))
+
+(cl-defun matlab-ts-mode--ei-get-new-line (&optional start-node start-offset)
+ "Get new line content with element spacing adjusted.
+Optional START-NODE and START-OFFSET are used to compute new pt-offset,
+the point offset in the line used to restore point after updating line.
+Note, new line content may be same as current line. Also computes
+line-node-types which is a string containing the line node types.
+Returns electric indent info, ei-info,
+ (list NEW-LINE-CONTENT PT-OFFSET LINE-NODE-TYPES FIRST-NODE-IN-LINE)
+or nil."
+ (save-excursion
+ (back-to-indentation)
+ (when (matlab-ts-mode--ei-no-elements-to-indent)
+ (cl-return-from matlab-ts-mode--ei-get-new-line))
+
+ ;; Compute ei-line, the electric indented line content
+ (let* (pt-offset ;; used in restoring point
+ (ei-line (buffer-substring (line-beginning-position) (point)))
+ (pair (matlab-ts-mode--ei-move-to-and-get-node))
+ (node (or (car pair)
+ (cl-return-from matlab-ts-mode--ei-get-new-line)))
+ (node-type (cdr pair))
+ (first-node node)
+ line-node-types
+ next2-pair ;; used when we have: (NODE-RE (NEXT-NODE-RE
NEXT2-NODE-RE) N-SPACES-BETWEEN)
+ next2-n-spaces-between)
+
+ (cl-loop
+ while (and (< (point) (line-end-position))
+ (< (treesit-node-end node) (line-end-position)))
+ do
+ (let* ((next-pair (progn
+ (goto-char (treesit-node-end node))
+ (or next2-pair
+ (matlab-ts-mode--ei-move-to-and-get-node))))
+ (next-node (let ((candidate-node (car next-pair)))
+ (when (not candidate-node)
+ (cl-return))
+ (when (= (treesit-node-start candidate-node)
+ (treesit-node-end candidate-node))
+ ;; Syntax errors can result in empty nodes, so
skip this line
+ ;; TopTester: electric_indent_empty_node_error.m
+ (cl-return-from matlab-ts-mode--ei-get-new-line))
+ candidate-node))
+ (next-node-type (cdr next-pair))
+ (n-spaces-between next2-n-spaces-between))
+
+ (setq next2-pair nil
+ next2-n-spaces-between nil)
+
+ (when matlab-ts-mode--electric-indent-assert
+ (setq line-node-types (matlab-ts-mode--ei-update-line-node-types
line-node-types
+ node
node-type)))
+
+ (when (not n-spaces-between)
+ (cl-loop for tuple in matlab-ts-mode--ei-spacing do
+ (let* ((node-re (nth 0 tuple))
+ (next-spec (nth 1 tuple))
+ (next-node-re (if (listp next-spec) (car next-spec)
next-spec))
+ (next2-node-re (when (listp next-spec) (cdr
next-spec))))
+
+ (when (and (string-match-p node-re node-type)
+ (string-match-p next-node-re next-node-type)
+ (or (not next2-node-re)
+ (save-excursion
+ (goto-char (treesit-node-end next-node))
+ (let* ((pair
(matlab-ts-mode--ei-move-to-and-get-node))
+ (next2-node-type
+ (or (cdr pair)
+ (cl-return-from
+
matlab-ts-mode--ei-get-new-line))))
+
+ (when (string-match-p next2-node-re
next2-node-type)
+ (setq next2-pair pair)
+ (setq next2-n-spaces-between (nth 2
tuple))
+ t)))))
+
+ (setq n-spaces-between (nth 2 tuple))
+ (when matlab-ts-mode--electric-indent-verbose
+ (message "-->ei-matched: %S for node=<\"%s\" %S>
next-node=<\"%s\" %S>"
+ tuple node-type node next-node-type
next-node))
+ (cl-return)))))
+
+ (when (not n-spaces-between)
+ (error "Internal error, unhandled node <\"%s\" %S> and next-node
<\"%s\" %S>"
+ node-type node next-node-type next-node))
+
+ (let* ((node-end (treesit-node-end node))
+ (next-node-start (treesit-node-start next-node))
+ (extra-chars (matlab-ts-mode--ei-node-extra-chars node-end
next-node-start)))
+ ;; Update ei-line
+ (when (equal start-node node)
+ (setq pt-offset (+ (length ei-line) start-offset)))
+
+ (setq ei-line (matlab-ts-mode--ei-concat-line ei-line node
extra-chars n-spaces-between))
+
+ (setq node next-node
+ node-type next-node-type)
+ )))
+
+ (when node
+ (when (equal start-node node)
+ (setq pt-offset (+ (length ei-line) start-offset)))
+ (when matlab-ts-mode--electric-indent-assert
+ (setq line-node-types (matlab-ts-mode--ei-update-line-node-types
line-node-types
+
node node-type)))
+ (let ((extra-chars (matlab-ts-mode--ei-node-extra-chars
+ (min (treesit-node-end node) (line-end-position))
+ (line-end-position))))
+ (setq ei-line (matlab-ts-mode--ei-concat-line ei-line node
extra-chars))))
+
+ (list ei-line pt-offset line-node-types first-node))))
+
+(cl-defun matlab-ts-mode--ei-m-matrix-first-col-extra (matrix)
+ "For MATRIX indent alignment, get first-col-extra."
+ ;; For first-col-extra consider the following where
matlab-ts-mode--array-indent-level is 2.
+ ;; In this case first-col-extra will be 1.
+ ;; m = [
+ ;; 1 2
+ ;; 3 4
+ ;; ];
+ (let ((first-col-extra (save-excursion
+ (goto-char (treesit-node-start matrix))
+ (forward-char) ;; step over the "["
+ (let ((found-element nil))
+ ;; found a matrix element?
+ (while (and (not found-element)
+ (re-search-forward "[^ \t]"
(line-end-position) t))
+ (backward-char)
+ (let ((node (treesit-node-at (point))))
+ (when (not (string-match-p (rx bos (or
"comment"
+
"line_continuation")
+ eos)
+ (treesit-node-type
node)))
+ (setq found-element t))
+ (goto-char (min (line-end-position)
+ (treesit-node-end node)))))
+ (if found-element 0 (1-
matlab-ts-mode--array-indent-level))))))
+ first-col-extra))
+
+(cl-defun matlab-ts-mode--ei-m-matrix-col-widths (matrix first-col-extra
&optional first-col-only)
+ "Get multi-line MATRIX column widths adding in FIRST-COL-EXTRA to first
column.
+If optional FIRST-COL-ONLY is non-nil, then return only the width of the
+first column in MATRIX.
+Returns alist where each element in the alist is (COLUMN-NUM . WIDTH)"
+ (let ((column-widths '())) ;; (alist-get column-num column-widths) ==> width
+ (dolist (m-child (treesit-node-children matrix))
+ (when (string= (treesit-node-type m-child) "row")
+ (let ((column-num 0))
+ (cl-loop
+ for entry in (treesit-node-children m-child)
+ do
+ (when (not (string= (treesit-node-type entry) ","))
+ (setq column-num (1+ column-num))
+ (let ((width (or (alist-get column-num column-widths) 0))) ;;
matrix element width
+ (let ((el-width (- (treesit-node-end entry) (treesit-node-start
entry))))
+ (when (> el-width width)
+ (if (= width 0)
+ (push `(,column-num . ,el-width) column-widths)
+ (setf (alist-get column-num column-widths) el-width)))))
+ (when first-col-only
+ (cl-return))
+ )))))
+
+ (when (> first-col-extra 0)
+ (let ((col1-width (+ (alist-get 1 column-widths) first-col-extra)))
+ (setf (alist-get 1 column-widths) col1-width)))
+
+ column-widths))
+
+(defun matlab-ts-mode--ei-get-m-matrix-row-in-line ()
+ "Given point within a matrix assignment statement, return row node.
+Note, nil may be returned when line is only a continuation, e.g.
+ v = [1 2; ...
+ ...
+ 3 4];
+when on the 2nd continuation only line, nil is returned."
+ (save-excursion
+ (back-to-indentation)
+ (let (row-node
+ found-ans)
+ (cl-loop
+ while (not found-ans) do
+
+ (let* ((node-at-pt (treesit-node-at (point)))
+ (node node-at-pt))
+ (while (and node
+ (not (string-match-p (rx bos (or "row" "matrix"
"assignment") eos)
+ (treesit-node-type node))))
+ (setq node (treesit-node-parent node)))
+
+ (if (and node
+ (string= (treesit-node-type node) "row"))
+ (setq found-ans t
+ row-node node)
+ (goto-char (min (treesit-node-end node-at-pt) (line-end-position)))
+ (when (not (re-search-forward "[^ \t]" (line-end-position) t))
+ (setq found-ans t)))))
+ row-node)))
+
+;; Internal variable that shouldn't be altered. It's used to avoid infinite
recursion.
+(defvar matlab-ts-mode--ei-align-enabled t)
+
+;; This is used to cache matrix alignments for indent-region
+;; It will be non-nil when called from indent-region.
+(defvar matlab-ts-mode--ei-align-matrix-alist nil)
+
+(defun matlab-ts-mode--ei-indent-matrix-in-tmp-buf (assign-node)
+ "Insert ASSIGN-NODE in to current tmp-buf and indent.
+Point is left at beginning of line containing the ASSIGN-NODE text."
+ (let (assign-str
+ n-levels)
+ (with-current-buffer (treesit-node-buffer assign-node)
+ (let* ((assign-start-pos (save-excursion (goto-char (treesit-node-start
assign-node))
+ (line-beginning-position)))
+ (assign-end-pos (save-excursion (goto-char (treesit-node-end
assign-node))
+ (line-end-position)))
+ (indent-spaces (- (treesit-node-start assign-node)
assign-start-pos)))
+ (setq assign-str (buffer-substring assign-start-pos assign-end-pos)
+ n-levels (if (= (mod indent-spaces matlab-ts-mode--indent-level)
0)
+ (/ indent-spaces matlab-ts-mode--indent-level)
+ ;; else: not at a standard level so no need to add
conditionals as the
+ ;; indent level will be corrected later.
+ 0))))
+
+ (cl-loop for level from 1 to n-levels do
+ (insert "if 1\n"))
+
+ (insert assign-str "\n")
+
+ (cl-loop for level from 1 to n-levels do
+ (insert "end\n"))
+
+ (matlab-ts-mode)
+
+ ;; Indent to adjust spacing among operators, but don't do other alignment
items
+ (let ((matlab-ts-mode--ei-align-enabled nil)
+ ;; t-utils-test-indent captures messages using
treesit--indent-verbose and we don't
+ ;; want to capture the messages from this temp indent-region.
+ (treesit--indent-verbose nil))
+ (indent-region (point-min) (point-max)))
+
+
+ (goto-char (point-min))
+ (when (> n-levels 0)
+ (forward-line n-levels))))
+
+(cl-defun matlab-ts-mode--ei-align-line-in-m-matrix (assign-node ei-info)
+ "Align current line with EI-INFO in a multi-line matrix of ASSIGN-NODE.
+See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
+
+ ;; TopTester:
test-matlab-ts-mode-electric-indent-files/electric_indent_matrix_cols.m
+ (when matlab-ts-mode--ei-align-matrix-alist ;; Use cached value?
+ (let ((ei-line (alist-get (line-number-at-pos)
matlab-ts-mode--ei-align-matrix-alist)))
+ (when ei-line
+ (cl-return-from matlab-ts-mode--ei-align-line-in-m-matrix (cons
ei-line (cdr ei-info))))))
+
+ (let* ((assign-start-linenum (line-number-at-pos (treesit-node-start
assign-node)))
+ (tmp-buf-ei-linenum (1+ (- (line-number-at-pos)
assign-start-linenum)))
+ (tmp-buf-row-linenum (if matlab-ts-mode--ei-align-matrix-alist 1
tmp-buf-ei-linenum))
+ (matrix-alist matlab-ts-mode--ei-align-matrix-alist))
+ (with-temp-buffer
+ (matlab-ts-mode--ei-indent-matrix-in-tmp-buf assign-node)
+
+ (let* ((matrix-node (treesit-node-parent (treesit-search-subtree
+ (treesit-buffer-root-node) (rx
bos "[" eos) nil t)))
+ (first-col-extra (matlab-ts-mode--ei-m-matrix-first-col-extra
matrix-node))
+ (column-widths (matlab-ts-mode--ei-m-matrix-col-widths
matrix-node first-col-extra)))
+
+ ;; Move to the line of interest when we called from
matlab-ts-mode--treesit-indent,
+ ;; otherwise calculate all matrix rows for indent-region.
+ (when (and (not matrix-alist)
+ (> tmp-buf-ei-linenum 1))
+ (forward-line (1- tmp-buf-ei-linenum)))
+
+ (while (not (eobp)) ;; Adjust column widths
+ (back-to-indentation)
+ (let* ((row-node (matlab-ts-mode--ei-get-m-matrix-row-in-line))
+ (indent-start-pt (point))
+ ;; line content does not have leading indent-level spaces
+ (content (buffer-substring indent-start-pt
(line-end-position)))
+ (ei-line (buffer-substring (line-beginning-position)
(line-end-position)))
+ n-spaces)
+ (when row-node
+ (let* ((col-num (length column-widths))
+ (pt-offset (nth 1 ei-info))
+ (matrix-offset (save-excursion
+ (goto-char (treesit-node-start
matrix-node))
+ (1+ (- (point)
(line-beginning-position)))))
+ (indent-offset (or (string-match-p "[^ \t]+" ei-line)
+ (error "Assert: no offset"))))
+
+ (when (< matrix-offset indent-offset)
+ (when pt-offset
+ (setq pt-offset (- pt-offset (- indent-offset
matrix-offset))))
+ (setq indent-offset matrix-offset))
+
+ (dolist (element (reverse (treesit-node-children row-node)))
+ (when (not (string= (treesit-node-type element) ",")) ;; at
a column?
+ (let ((width (or (alist-get col-num column-widths)
+ (error "Assert: no col width")))
+ (curr-width (- (treesit-node-end element)
(treesit-node-start element))))
+ (setq n-spaces (- width curr-width))
+ (when (< curr-width width)
+ (let ((offset (- (treesit-node-start element)
indent-start-pt)))
+ (when (and pt-offset
+ (or (= indent-start-pt (point-min))
+ (<= (+ offset matrix-offset)
pt-offset)))
+ (setq pt-offset (+ pt-offset n-spaces)))
+ (setq content (concat (substring content 0 offset)
+ (make-string n-spaces ? )
+ (substring content offset)))))
+ (setq col-num (1- col-num)))))
+
+ (setq ei-line (concat (substring ei-line 0 indent-offset)
content))
+ (when (= tmp-buf-row-linenum tmp-buf-ei-linenum)
+ (setq ei-info (list ei-line pt-offset (nth 2 ei-info) (nth 3
ei-info))))))
+
+ (when matrix-alist
+ (let* ((buf-linenum (1- (+ assign-start-linenum
tmp-buf-row-linenum))))
+ (push `(,buf-linenum . ,ei-line) matrix-alist)))
+
+ (if (not matrix-alist)
+ (goto-char (point-max))
+ (forward-line)
+ (setq tmp-buf-row-linenum (1+ tmp-buf-row-linenum)))))))
+
+ (when matrix-alist
+ (setq matlab-ts-mode--ei-align-matrix-alist matrix-alist)))
+ ;; ei-info for current line
+ ei-info)
+
+(cl-defun matlab-ts-mode--ei-matrix-ends-on-line (matrix)
+ "Does MATRIX end on a line by itself?"
+ (save-excursion
+ (goto-char (treesit-node-end matrix))
+ (while (re-search-forward "[^ \t]" (line-end-position) t)
+ (backward-char)
+ (let ((node (treesit-node-at (point))))
+ (when (not (string-match-p (rx bos (or "," ";" "comment"
"line_continuation") eos)
+ (treesit-node-type node)))
+ (cl-return-from matlab-ts-mode--ei-matrix-ends-on-line))
+ (goto-char (treesit-node-end node)))))
+ t)
+
+(cl-defun matlab-ts-mode--ei-is-m-matrix (matrix)
+ "Is MATRIX node a multi-line matrix?
+We define a a multi-line matrix has one row per line and more than one
+column."
+ (let ((start-line (line-number-at-pos (treesit-node-start matrix)))
+ (end-line (line-number-at-pos (treesit-node-end matrix)))
+ (n-rows 0)
+ n-cols)
+ (when (and (> end-line start-line) ;; multi-line matrix?
+ (matlab-ts-mode--ei-matrix-ends-on-line matrix)
+ (not (treesit-search-subtree matrix (rx bos "ERROR" eos) nil
t)))
+ (dolist (child (treesit-node-children matrix))
+ (let ((child-type (treesit-node-type child)))
+ (cond
+ ((string= child-type "row")
+ (let ((row-start-line-num (line-number-at-pos (treesit-node-start
child))))
+ ;; Return nil if row not on one line
+ (when (not (= row-start-line-num (line-number-at-pos
(treesit-node-end child))))
+ (cl-return-from matlab-ts-mode--ei-is-m-matrix))
+ ;; Return nil if more than one row one the line
+ (let ((next-node (treesit-node-next-sibling child)))
+ (when (and (string= (treesit-node-type next-node) "row")
+ (= row-start-line-num (line-number-at-pos
+ (treesit-node-start
next-node))))
+ (cl-return-from matlab-ts-mode--ei-is-m-matrix)))
+ ;; Return nil if row contains sub-matrices
+ (when (treesit-search-subtree child (rx bos (or "[" "]") eos)
nil t)
+ (cl-return-from matlab-ts-mode--ei-is-m-matrix))
+
+ (setq n-rows (1+ n-rows))
+
+ ;; Count the columns
+ (let ((n-cols-in-row 0))
+ (dolist (el (treesit-node-children child))
+ (when (not (string= (treesit-node-type el) ","))
+ (setq n-cols-in-row (1+ n-cols-in-row))))
+ (if (not n-cols)
+ (setq n-cols n-cols-in-row)
+ (if (not (= n-cols n-cols-in-row))
+ (cl-return-from matlab-ts-mode--ei-is-m-matrix))))
+ ))
+ ;; Case unexpected matrix child node
+ ((not (string-match-p (rx bos (or "[" "]" "comment"
"line_continuation") eos)
+ child-type))
+ (error "Assert: unexpected matrix child %S" child))))))
+ ;; Matrix with more than one row and more than one column where each row
is on its own line?
+ (and (> n-rows 1) (>= n-cols 1))))
+
+(defun matlab-ts-mode--ei-is-assign (first-node type)
+ "Is FIRST-NODE of line for an assignment that matches TYPE?
+TYPE can be \\='single-line or \\='multi-line-matrix. When
+\\='multi-line-matrix, the assignment is to a matrix with two or more
+rows and two or more columns, where each row is on its own line. The
+assignment node is return or nil."
+ (let ((assign-node (treesit-node-parent first-node)))
+ (while (and assign-node
+ (not (string= (treesit-node-type assign-node) "assignment")))
+ (setq assign-node (treesit-node-parent assign-node)))
+ (when assign-node
+ (save-excursion
+ (beginning-of-line)
+ (when (re-search-forward "=" (line-end-position) t)
+ (backward-char)
+ (let ((eq-node (treesit-node-at (point))))
+ ;; First "=" must be an assignment (assumptions elsewhere require
this).
+ (when (and (equal (treesit-node-type eq-node) "=")
+ (equal (treesit-node-type (treesit-node-parent
eq-node)) "assignment"))
+ (cond
+ ;; Single-line assignment? Example: v1 = [1, 2];
+ ((eq type 'single-line)
+ (when (<= (treesit-node-end assign-node) (line-end-position))
+ assign-node))
+
+ ;; Multi-line matrix assignment? Example: m1 = [1 2
+ ;; 3 4];
+ ((eq type 'multi-line-matrix)
+ (goto-char (treesit-node-end eq-node))
+ (let ((next-node (treesit-node-next-sibling eq-node)))
+ (while (equal (treesit-node-type next-node)
"line_continuation")
+ (setq next-node (treesit-node-next-sibling next-node)))
+ (when (and (equal (treesit-node-type next-node) "matrix")
+ (matlab-ts-mode--ei-is-m-matrix next-node))
+ assign-node)))
+ (t
+ (error "Assert: bad type %S" type))))))))))
+
+(defun matlab-ts-mode--ei-point-in-m-matrix (ei-info)
+ "Are we in a multi-line matrix?
+See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents. Returns
+mat-info a (list matrix-node n-rows n-cols) if in a multi-line matrix."
+ (let* ((first-node-in-line (nth 3 ei-info))
+ (parent (treesit-node-parent first-node-in-line)))
+ (while (and parent
+ (not (string= (treesit-node-type parent) "assignment")))
+ (setq parent (treesit-node-parent parent)))
+ (when parent ;; In an assignment?
+ (save-excursion
+ (goto-char (treesit-node-start parent))
+ (beginning-of-line)
+ (back-to-indentation)
+ (let ((first-node (treesit-node-at (point))))
+ (matlab-ts-mode--ei-is-assign first-node 'multi-line-matrix))))))
+
+(defun matlab-ts-mode--ei-assign-offset (ei-line)
+ "Get the assignment offset from the indent-level in EI-LINE."
+ (let* ((first-char-offset (or (string-match-p "[^ \t]" ei-line) (error
"Assert: no first char")))
+ (offset (- (or (string-match-p "=" ei-line) (error "Assert: no ="))
+ first-char-offset)))
+ offset))
+
+;; This is used to cache aligned assignments for indent-region
+(defvar-local matlab-ts-mode--ei-align-assign-alist nil)
+
+(defun matlab-ts-mode--ei-align-assignments (ei-info)
+ "Update EI-INFO to align assignments.
+See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
+ (let ((first-node-in-line (nth 3 ei-info)))
+ (when (matlab-ts-mode--ei-is-assign first-node-in-line 'single-line)
+ (let* ((ei-line (nth 0 ei-info))
+ (line-assign-offset (matlab-ts-mode--ei-assign-offset ei-line))
+ assign-offset
+ line-nums
+ line-start-pt)
+
+ (when (or (not matlab-ts-mode--ei-align-assign-alist)
+ (not (setq assign-offset (alist-get (line-number-at-pos)
+
matlab-ts-mode--ei-align-assign-alist))))
+ (setq assign-offset line-assign-offset)
+ (setq line-nums `(,(line-number-at-pos)))
+ (save-excursion
+ (beginning-of-line)
+ (setq line-start-pt (point))
+
+ ;; Look backwards and then forwards for single-line assignments
+ (cl-loop
+ for direction in '(-1 1) do
+ (goto-char line-start-pt)
+ (cl-loop
+ while (not (if (= direction -1) (bobp) (eobp))) do
+ (forward-line direction)
+ (let* ((l-info (matlab-ts-mode--ei-get-new-line))
+ (l-first-node (nth 3 l-info)))
+ (if (and l-first-node
+ (matlab-ts-mode--ei-is-assign l-first-node
'single-line))
+ (let ((l-offset (matlab-ts-mode--ei-assign-offset (nth 0
l-info))))
+ (push (line-number-at-pos) line-nums)
+ (when (> l-offset assign-offset)
+ (setq assign-offset l-offset)))
+ (cl-return))))))
+ (when matlab-ts-mode--ei-align-assign-alist
+ (dolist (line-num line-nums)
+ (push `(,line-num . ,assign-offset)
matlab-ts-mode--ei-align-assign-alist))))
+
+ (let ((diff (- assign-offset line-assign-offset)))
+ (when (> diff 0)
+ (let* ((loc (1- (string-match "=" ei-line)))
+ (new-pt-offset (let ((pt-offset (nth 1 ei-info)))
+ (when pt-offset
+ (if (<= loc pt-offset)
+ (+ pt-offset diff)
+ pt-offset)))))
+ (setq ei-line (concat (substring ei-line 0 loc)
+ (make-string diff ? )
+ (substring ei-line loc)))
+ (setq ei-info (list ei-line new-pt-offset (nth 2 ei-info) (nth 3
ei-info)))))))))
+ ei-info)
+
+(defun matlab-ts-mode--ei-trailing-comment-offset (ei-info)
+ "Get trailing comment offset from first char current line?
+See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents.
+To simplify implementation, we require that the first \"%\" character in
+the line be the start of the trailing comment. Thus,
+ s = \"foo % bar\" % comment
+is not identified as a trailing comment and
+ s = \"foo bar\" %comment
+is identified as a trailing comment."
+ (when ei-info
+ (save-excursion
+ (beginning-of-line)
+ (when (re-search-forward "%" (line-end-position) t)
+ (let ((node (treesit-node-at (point))))
+ (when (equal (treesit-node-type node) "comment")
+ (let* ((new-line (nth 0 ei-info))
+ (offset (- (string-match-p "%" new-line) (string-match-p
"[^ \t]" new-line))))
+ (when (> offset 0)
+ ;; have trailing comment at offset from first char in new-line
+ offset))))))))
+
+;; This is used to cache comment alignments for indent-region
+(defvar-local matlab-ts-mode--ei-align-comment-alist nil)
+
+(defun matlab-ts-mode--ei-align-trailing-comments (ei-info)
+ "Align trailing comments in EI-INFO.
+See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
+ (let* ((line-comment-offset (matlab-ts-mode--ei-trailing-comment-offset
ei-info)))
+ (when line-comment-offset
+ (let* (comment-offset
+ line-nums
+ line-start-pt)
+
+ (when (or (not matlab-ts-mode--ei-align-comment-alist)
+ (not (setq comment-offset (alist-get (line-number-at-pos)
+
matlab-ts-mode--ei-align-comment-alist))))
+ (setq comment-offset line-comment-offset)
+ (setq line-nums `(,(line-number-at-pos)))
+ (save-excursion
+ (beginning-of-line)
+ (setq line-start-pt (point))
+
+ ;; Look backwards and then forwards for lines with trailing
comments
+ (cl-loop
+ for direction in '(-1 1) do
+ (goto-char line-start-pt)
+ (cl-loop
+ while (not (if (= direction -1) (bobp) (eobp))) do
+ (forward-line direction)
+ (let* ((ei-l-info (matlab-ts-mode--ei-get-new-line))
+ (l-offset (matlab-ts-mode--ei-trailing-comment-offset
ei-l-info)))
+ (if l-offset
+ (progn
+ (push (line-number-at-pos) line-nums)
+ (when (> l-offset comment-offset)
+ (setq comment-offset l-offset)))
+ (cl-return))))))
+
+ (when matlab-ts-mode--ei-align-comment-alist
+ (dolist (line-num line-nums)
+ (push `(,line-num . ,comment-offset)
matlab-ts-mode--ei-align-comment-alist))))
+
+ (let ((diff (- comment-offset line-comment-offset)))
+ (when (> diff 0)
+ (let* ((ei-line (nth 0 ei-info))
+ (loc (1- (string-match "%" ei-line)))
+ (new-pt-offset (let ((pt-offset (nth 1 ei-info)))
+ (when pt-offset
+ (if (<= loc pt-offset)
+ (+ pt-offset diff)
+ pt-offset)))))
+ (setq ei-line (concat (substring ei-line 0 loc)
+ (make-string diff ? )
+ (substring ei-line loc)))
+ (setq ei-info (list ei-line new-pt-offset (nth 2 ei-info) (nth 3
ei-info))))))))
+ ei-info))
+
+(defun matlab-ts-mode--ei-align (ei-info)
+ "Align elements in EI-INFO.
+See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
+ (let ((matrix-node (matlab-ts-mode--ei-point-in-m-matrix ei-info)))
+ (if matrix-node
+ (setq ei-info (matlab-ts-mode--ei-align-line-in-m-matrix matrix-node
ei-info)))
+ ;; else do single-line alignments
+ (setq ei-info (matlab-ts-mode--ei-align-assignments ei-info))
+ (setq ei-info (matlab-ts-mode--ei-align-trailing-comments ei-info)))
+ ei-info)
+
+(cl-defun matlab-ts-mode--ei-indent-elements-in-line (&optional start-node
start-offset)
+ "Indent current line by adjust spacing around elements.
+Optional START-NODE and START-OFFSET are used to restore the point when
+line is updated. Returns t if line was updated."
+
+ ;; If line was indented (ei-line is not same as current line), then update
the buffer
+ (let ((ei-info (matlab-ts-mode--ei-get-new-line start-node start-offset)))
+ (when ei-info
+ (when matlab-ts-mode--ei-align-enabled
+ (setq ei-info (matlab-ts-mode--ei-align ei-info)))
+ (let* ((ei-line (nth 0 ei-info))
+ (pt-offset (nth 1 ei-info))
+ (line-node-types (nth 2 ei-info))
+ (curr-line (buffer-substring (line-beginning-position)
(line-end-position)))
+ (updated (not (string= curr-line ei-line))))
+
+ (when updated
+ (delete-region (line-beginning-position) (line-end-position))
+ (insert ei-line)
+ (when matlab-ts-mode--electric-indent-assert
+ (matlab-ts-mode--ei-assert-match line-node-types))
+ (when pt-offset
+ (goto-char (+ (line-beginning-position) pt-offset))))
+ ;; result
+ updated))))
+
+(provide 'matlab-ts-mode--ei)
+;;; matlab-ts-mode--ei.el ends here
diff --git a/matlab-ts-mode.el b/matlab-ts-mode.el
index 1a705904ab..b6ffe597e9 100644
--- a/matlab-ts-mode.el
+++ b/matlab-ts-mode.el
@@ -8,7 +8,7 @@
;; Created: Jul-7-2025
;; Keywords: MATLAB
-;; Copyright (C) 2025 Free Software Foundation, Inc.
+;; Copyright (C) 2025-2026 Free Software Foundation, Inc.
;;
;; This file is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published
@@ -44,6 +44,7 @@
(require 'matlab-is-matlab-file)
(require 'matlab-sections)
(require 'matlab-ts-mode--builtins)
+(require 'matlab-ts-mode--ei)
;;; Customizations
@@ -92,42 +93,6 @@ Then restart Emacs and run
You can also install via use-package or other methods."
:type 'boolean)
-(defcustom matlab-ts-mode-electric-indent t
- "*If t, indent (format) language elements within code.
-- Canonicalize language elements spacing
- Example | Result
- ----------------------------- | -----------------------------
- a = b+ c *d ; | a = b + c * d;
-
-- Align consecutive assignments
- Example | Result
- ----------------------------- | -----------------------------
- width = 100; | width = 100;
- length = 200; | length = 200;
- area=width * length; | area = width * length;
-
-- Align trailing comments
- Example | Result
- ----------------------------- | -----------------------------
- a = myFcn1(1, 2); % comment 1 | a = myFcn1(1, 2); % comment 1
- a = a + 5; % comment 2 | a = a + 2; % comment 2
-
-- Align matrix columns
- Example | Result
- ----------------------------- | -----------------------------
- m = [2,4000 | m = [ 2, 4000
- 3000,1] | 3000, 1]
-
-- Align properties
- Example:
- TODO
- is indented as:
- TODO"
- :type 'boolean)
-
-(defvar matlab-ts-mode--electric-indent-verbose nil)
-(defvar matlab-ts-mode--electric-indent-assert nil)
-
(defcustom matlab-ts-mode-electric-ends t
"*If t, insert end keywords to complete statements and insert % for doc
comments.
For example, if you type
@@ -1030,10 +995,12 @@ Example, disp variable is overriding the disp builtin
function:
;; Having one-style of consistent indentation makes reading others' code
easier and thus
;; we do not provide indent customization.
;;
-;; 1. Indent level of 4 spaces, no TAB characters, unicode, LF line-endings
(no CRLF)
+;; 1. Indent level of 4 spaces, no TAB characters, unicode, LF line-endings
(no CRLF).
+;; [Implemented in the indent-engine]
;;
-;; 2. Code and comments should fit within 100 columns.A line may exceed 100
characters if it
+;; 2. Code and comments should fit within 100 columns. A line may exceed 100
characters if it
;; improves code readability.
+;; [Guidance - not implemented in the indent engine]
;;
;; In general, too much logic on a given line hurts readability. Reading
the first part of a
;; line should convey the operation being performed by the line. Adding
unrelated concepts on a
@@ -1061,7 +1028,10 @@ Example, disp variable is overriding the disp builtin
function:
;; Adding the ability to explicitly re-flowing of code in a region, similar
to the way M-q or
;; `fill-paragraph' works in a comment, would be a nice addition.
;;
-;; 3. Use 2-space offset for case labels. The code under case or otherwise
statements is one
+;; 3. Use 2-space offset for case labels.
+;; [Implemented in the indent engine]
+;;
+;;; The code under case or otherwise statements is one
;; condition and hence should have the same indent level anchored from the
switch statement.
;; The level of complexity of the following two statements is the same
which is clear from
;; the level of indent of the doIt function call.
@@ -1072,6 +1042,7 @@ Example, disp variable is overriding the disp builtin
function:
;; | end
;;
;; 4. Cells and arrays contain data and have indent level of 2 spaces.
+;; [Implemented in the indent engine]
;;
;; Indents cells and array with an inner indent level of 2 spaces for the
data. Cells and arrays
;; which are structured data. Since there's no "conditionals" within
structured data, we treat
@@ -1086,7 +1057,9 @@ Example, disp variable is overriding the disp builtin
function:
;; } ...
;; }
;;
-;; 5. Operator padding.
+;;
+;; 5. Canonicalize language elements spacing.
+;; [Implemented in the indent engine]
;;
;; - Use a single space after (but not before) a comma.
;;
@@ -1109,9 +1082,9 @@ Example, disp variable is overriding the disp builtin
function:
;; end
;; end
;;
-;; TODO [future] add operator padding to indent engine
-;;
;; 6. Function call formats
+;; [Implemented in the indent engine]
+;;
;; - On a single line:
;;
;; [result1, result2] = myFunction(arg1, arg2)
@@ -1133,6 +1106,7 @@ Example, disp variable is overriding the disp builtin
function:
;; arg2) % arg2 should be
aligned with arg1
;;
;; 7. Function definition formats
+;; [Implemented in the indent engine]
;;
;; - On a single line
;; function [out1, out2] = myFunction(in1, in2)
@@ -1147,6 +1121,7 @@ Example, disp variable is overriding the disp builtin
function:
;; in2) % comment
;;
;; 8. Expressions
+;; [Guidance - not implemented in the indent engine]
;;
;; When you have long expressions, be consistent in how you break up the
lines and minimize use
;; of newlines. Use newlines to help with readability. Place operators at
the end of a line,
@@ -1192,60 +1167,37 @@ Example, disp variable is overriding the disp builtin
function:
;; if (c > 30 && d > 40) || e if (((c > 30) && (d > 40)) || e)
;; end end
;;
-;;
;; 9. Consecutive statement alignment
+;; [Implemented in the indent engine]
;;
-;; Be consistent in the alignment of consecutive statements. For example,
-;;
+;; Example:
;; width = 5;
;; length = 10;
;; area = width * length;
;;
-;; alternatively you can un-align them:
-;;
-;; width = 5;
-;; length = 10;
-;; area = width * length;
-;;
-;; Don't mix the alignment
-;;
-;; width = 5;
-;; length = 10;
-;; area = width * length; % Bad partially aligned
-;;
-;; TODO [future] add consecutive statement alignment to the indent engine
-;;
-;; 10. Align consecutive trailing comments
-;;
-;; 11. Function/classdef doc help should be aligned with the function/classdef
keyword.
+;; 10. Align properties
+;; TODO
;;
-;; 12. Tabular data alignment
+;; 11. Align consecutive trailing comments
+;; [Implemented in the indent engine]
;;
-;; Be consistent in data alignment, either aligned:
+;; Example:
+;; width = 5; % width of rectangle
+;; length = 10; % length of rectangle
+;; area = width * length; % area of rectangle
;;
-;; table1 = [1, 2;
-;; 1000, 1.9;
-;; 100000, 1.875];
-;; or un-aligned
- ;
-;; table1 = [1, 2;
-;; 1000, 1.9;
-;; 100000, 1.875];
+;; 12. Function/classdef doc help should be aligned with the function/classdef
keyword.
+;; [Implemented in the indent engine]
;;
-;; TODO [future] add an alignment directive, %$align
+;; 13. Tabular data alignment
+;; [Implemented in the indent engine]
;;
-;; Alignment of tabular data should be done when there's an indent
-;; directive, perhaps named %$align which must precede the data to be
-;; aligned. For example, table1 would have it's columns aligned,
-;; whereas table2 would not:
+;; table1 = [ 1, 2
+;; 1000, 1.9
+;; 100000, 1.875]
;;
-;; % Units Cost (%$align)
-;; % ----- -----
-;; table1 = [1, 2;
-;; 1000, 1.9;
-;; 100000, 1.875];
-;;
-;; 13. Indent must follow the above rules when the code has syntax errors.
+;; 14. Indent must follow the above rules when the code has syntax errors.
+;; [Implemented in the indent engine]
(defvar matlab-ts-mode--indent-level 4
"Indentation level.")
@@ -2899,955 +2851,6 @@ Example:
))
"Tree-sitter indent rules for `matlab-ts-mode'.")
-(defvar matlab-ts-mode--ei-keywords-re
- (rx bos (or "arguments" ;; not technically a keyword (can be a variable),
but treat as a keyword
- "break"
- "case"
- "catch"
- "classdef"
- "continue"
- "else"
- "elseif"
- "end"
- "enumeration"
- "events"
- "for"
- "function"
- "get." ;; when used in a classdef method, e.g. function value =
get.propName(obj)
- "global"
- "if"
- "methods"
- "otherwise"
- "parfor"
- "persistent"
- "properties"
- "return"
- "set." ;; when used in a classdef method, e.g. function obj =
set.propName(obj, val)
- "spmd"
- "switch"
- "try"
- "while")
- eos))
-
-(defvar matlab-ts-mode--ei-pad-op-re
- (rx bos (or "+" "-" "*" "/" ".*" "./" "\\"
- "==" "~=" ">" ">=" "<" "<="
- "&" "|" "&&" "||"
- "="
- "^" ".^"
- "'" ".'"
- ":")
- eos))
-
-(defvar matlab-ts-mode--ei-0-after-re
- (rx bos (or "[" "{" "(" "~" "unary-op") eos))
-
-(defvar matlab-ts-mode--ei-val-re (rx bos (or "identifier" "number") eos))
-
-;; TODO optimize following by grouping together, also improve comments
-(defvar matlab-ts-mode--electric-indent-spacing
- ;; In a given line, we walk across the nodes adjusting spaces between NODE
and NEXT-NODE to
- ;; have N-SPACES-BETWEEN them.
- ;;
- ;; NODE-RE NEXT-NODE-RE
N-SPACES-BETWEEN
- `(
-
- ("." ,(rx bos (or "comment"
"line_continuation") eos) 1)
-
-
- ;; Case: property dimension
- ;; foo1 (1, :) {mustBeNumeric, mustBeReal} = [0, 0, 0];
- ("." ,(rx bos "prop-dim" eos)
0)
-
- ;; Case; [email protected];
- ;; TopTester:
tests/test-matlab-ts-mode-electric-indent-files/electric_indent_call_super.m
- ("." ,(rx bos "@-fcn-call" eos)
0)
- (,(rx bos "@-fcn-call" eos) "."
0)
-
- ;; Case: a.?b, M', M.'
- ("." ,(rx bos (or ".?" "'" ".'") eos)
0)
- (,(rx bos ".?" eos) "."
0)
-
- ;; Case: power and transpose: a^b a.^b
- (,matlab-ts-mode--ei-val-re (,(rx bos (or "^" ".^") eos) .
,matlab-ts-mode--ei-val-re) 0)
-
- ;; Case: anything followed by ",", etc.: [a, b]
- ("." ,(rx bos (or "," ";" ".") eos)
0)
-
- ;; Case lambda: @(x), metaclass operator, ?
- (,(rx bos (or "@" "?") eos) "."
0)
-
- ;; Case: anything after a ".", e.g. foo.bar, s.(fieldName)
- (,(rx bos "." eos) "."
0)
-
- (,(rx bos (or "," ";" "command_argument" "command_name") eos) "."
1)
-
- (,matlab-ts-mode--ei-0-after-re "."
0)
-
- (,(rx bos "]" eos) ,(rx bos (or "," ";") eos)
0)
- (,(rx bos "]" eos) ,(rx bos "[" eos)
1)
- ("." ,(rx bos (or "]" ")" "}") eos)
0)
-
- ;; Case: ") identifier" as in: propName (1, 1) double
- ;; arguments: g (1,1) {mustBeNumeric, mustBeReal}
- ;; @(x) ((ischar(x) || isstring(x)));
- (,(rx bos ")" eos) ,(rx bos (or "identifier" "{" "(") eos)
1)
-
- ;; Case: property identifier: propName (1,1) double
- (,(rx bos "property-id" eos) "."
1)
-
- ;; Case: padded operators, e.g.: a || b
- (,matlab-ts-mode--ei-pad-op-re "."
1)
- ("." ,matlab-ts-mode--ei-pad-op-re
1)
-
- ;; Case: string followed by anything, e.g. ["string1" foo(1)]
- (,(rx bos "string" eos) "."
1)
-
- ;; Case: anything before string, e.g. [foo(1) "string1"]
- ("." ,(rx bos "string" eos)
1)
-
- ;; Case: c3 = {b [c '%']};
- ("." ,(rx bos "[" eos)
1)
-
- (,(rx bos "identifier" eos) ,(rx bos (or "(" "{") eos)
0)
- (,(rx bos "identifier" eos) "."
1)
-
- ;; Case: number in matrix: [123 456]
- (,(rx bos "number" eos) "."
1)
-
- ;; Case: subclass
- (,(rx bos "<" eos) "."
1)
-
- ;; Case: keywords, e.g. if condition
- (,matlab-ts-mode--ei-keywords-re "."
1)
-
- ;; Case: c = {['freq' '%'] num2str(2)};
- (,(rx bos "]" eos) "."
1)
-
- ;; Case: c4{1} = [1 2; 3 4];
- ;; v4 = [c4{1}(1,1), c4{1}(1,1)];
- (,(rx bos "}" eos) ,(rx bos "(" eos)
0)
-
- ;; Case: ")": m3 = uint8([ones(20,1); 2*ones(8,1)]);
- ;; ^ ^
- ;; Case: c3 = {{[17.50 0] [17.50 0]} {[120 0] [120 20]}};
- ;; ^ ^
- (,(rx bos (or ")" "}") eos) "."
1)
- ))
-
-(cl-defun matlab-ts-mode--move-to-and-get-node ()
- "Move to and return node.
-Will return nil if no next node before end-of-line.
-Assumes point is at of current node or beginning of line."
- ;; Move point to first non-whitespace char
- (let ((eol (line-end-position)))
- (when (looking-at "[ \t]")
- (when (not (re-search-forward "[^ \t]" eol t))
- (cl-return-from matlab-ts-mode--move-to-and-get-node))
- (backward-char))
-
- (let ((node (treesit-node-at (point)))
- node-type)
-
- ;; Consider [[1,2];[3,4]] when point is on semicolon, node will be the
prior "]" because the
- ;; semicolon is an ignored node, so move forward to get to the "[" after
the semicolon.
- (while (and (not (eobp))
- (let ((node-start (treesit-node-start node)))
- (and (>= node-start (line-beginning-position))
- (< node-start (point)))))
- (forward-char)
- (setq node (treesit-node-at (point))))
-
- ;; Don't go past end-of-line point
- (when (or (equal "\n" (treesit-node-type node))
- (> (treesit-node-start node) eol)
- ;; When we get to EOL and in error context, node start will be
on an earlier line
- ;; x = [
- ;; TAB> 1 , 2 ;
- (< (treesit-node-start node) (line-beginning-position)))
- (goto-char eol)
- (setq node nil))
-
- (when node
- (setq node-type (treesit-node-type node))
- (let* ((parent (treesit-node-parent node))
- (parent-type (treesit-node-type parent)))
- (cond
- ;; Use string and not the elements of the string
- ((equal parent-type "string")
- (setq node parent
- node-type parent-type))
-
- ;; convert property identifier to property-id node-type
- ((and (equal node-type "identifier")
- (or
- ;; propertyWithOutDot?
- (and (equal parent-type "property")
- (equal (treesit-node-child parent 0) node))
- ;; property.nameWithDot?
- (and (equal parent-type "property_name")
- (equal (treesit-node-child (treesit-node-parent parent)
0) parent))))
- (setq node-type "property-id"))
-
- ;; Unary operator sign, + or -, e.g. [0 -e] or g = - e
- ((and (equal parent-type "unary_operator")
- (equal (treesit-node-child parent 0) node))
- (setq node-type "unary-op"))
-
- ;; Super-class constructor call
- ;; [email protected];
- ((and (equal node-type "@")
- (equal parent-type "function_call"))
- (setq node-type "@-fcn-call"))
-
- ;; Property dimensions
- ;; foo1 (1, :) {mustBeNumeric, mustBeReal} = [0, 0, 0];
- ((and (or (equal node-type "number") (equal node-type ":"))
- (or (equal parent-type "dimensions")
- (and (equal parent-type "spread_operator")
- (equal (treesit-node-type (treesit-node-parent
parent))
- "dimensions"))))
- (setq node-type "prop-dim"))
- )))
- (cons node node-type))))
-
-(defun matlab-ts-mode--electric-indent-assert-match (line-node-types)
- "Assert that LINE-NODE-TYPES string matches current line."
- (back-to-indentation)
- (let (curr-line-node-types)
- (cl-loop
- while (< (point) (line-end-position))
- do
- (let* ((pair (matlab-ts-mode--move-to-and-get-node))
- (node (or (car pair)
- (cl-return)))
- (node-type (cdr pair)))
- (setq curr-line-node-types
- (matlab-ts-mode--update-line-node-types curr-line-node-types
- node node-type))
- (let ((node-end (treesit-node-end node)))
- (if (< node-end (line-end-position))
- (goto-char node-end)
- (goto-char (line-end-position))))))
-
- (when (not (string= curr-line-node-types line-node-types))
- (error "Assert: line-node-types mismatch \"%s\" !EQ \"%s\" at line %d in
%s"
- curr-line-node-types line-node-types (line-number-at-pos (point))
(buffer-name)))))
-
-(defun matlab-ts-mode--concat-ei-line (ei-line node extra-chars &optional
n-spaces-to-append)
- "Return concat EI-LINE with NODE text.
-NODE-END is the NODE end accounting for ignored nodes (semicolons).
-EXTRA-CHARS are appended to EL-LINE.
-N-SPACES-TO-APPEND is the number of spaces to append between nodes."
-
- (let* ((node-end (treesit-node-end node))
- (last-pt (if (< node-end (line-end-position)) node-end
(line-end-position))))
- (concat ei-line
- (buffer-substring (treesit-node-start node) last-pt)
- extra-chars
- (if (not n-spaces-to-append) ;; last node?
- ;; Add trailing whitespace when in an ERROR node. Consider
- ;; switch a
- ;; case ;; One trailing whitespace
- ;; end
- ;; TopTester: electric_indent_xr_switch.m
- (when (and (treesit-parent-until node (rx bos "ERROR" eos))
- (< last-pt (line-end-position)))
- (save-excursion
- (end-of-line)
- (when (re-search-backward "[^ \t]"
(line-beginning-position) t)
- (forward-char)
- (when (not (= (point) (line-end-position)))
- (buffer-substring (point) (line-end-position))
- ))))
- (when (> n-spaces-to-append 0)
- (make-string n-spaces-to-append ? ))))))
-
-(defun matlab-ts-mode--no-elements-to-indent ()
- "Return t if no elements in the current line to indent.
-Assumes that current point is at `back-to-indentation'."
- (or
- ;; (1) Empty line?
- (not (looking-at "[^ \t\n\r]"))
- ;; (2) Comment line? Nothing to indent in line if it's a comment line.
- (let ((first-node-type (or (treesit-node-type (treesit-node-at (point)))
"")))
- (string-match-p (rx bos (or "line_continuation" "comment") eos)
first-node-type))
- ;; (3) Syntax error *within* the line? If error node covers whole line,
assume nodes in
- ;; line are good, i.e. electric indent the line.
- (let ((beg (line-beginning-position))
- (end (line-end-position))
- (capture-errors (treesit-query-capture (treesit-buffer-root-node)
'((ERROR) @e))))
- (cl-loop
- for capture-error in capture-errors do
- (let* ((error-node (cdr capture-error))
- (error-start (treesit-node-start error-node))
- (error-end (treesit-node-end error-node)))
- (when (and (> error-start beg)
- (< error-end end))
- (cl-return t))
- )))))
-
-(defun matlab-ts-mode--node-extra-chars (node-end next-node-start)
- "Get extra chars after NODE-END and before NEXT-NODE-START."
-
- (let ((extra-chars ""))
- ;; Handle ignored characters, e.g. ";" in matrices where node="]",
next-node="["
- ;; [[1, 2]; [3, 4]]
- ;; ^ ^
- (goto-char node-end)
- (when (or ;; [[1,2];[3,4]]?
- (and (< node-end next-node-start)
- (looking-at "[^ \t]"))
- ;; [[1,2] ; [3,4]]?
- ;; or a multiline matrix:
- ;; x = [ 1 , 2 ;
- (save-excursion (and (when (re-search-forward "[^ \t]"
next-node-start t)
- (backward-char)
- t)
- (< (point) next-node-start))))
- (while (< (point) next-node-start)
- (while (and (< (point) next-node-start)
- (looking-at "[^ \t]"))
- (setq extra-chars (concat extra-chars (match-string 0)))
- (forward-char)
- (setq node-end (point)))
- (re-search-forward "[ \t]+" next-node-start t)))
- extra-chars))
-
-(defun matlab-ts-mode--update-line-node-types (line-node-types node node-type)
- "Append NODE-TYPE of NODE to LINE-NODE-TYPES."
- (if (and (string= node-type ";")
- (string= (treesit-node-type (treesit-node-parent node)) "matrix"))
- ;; Ignore ';' row separator in matrix because these may be ignored by
tree-sitter
- line-node-types
- (concat line-node-types (when line-node-types " ") node-type)))
-
-(cl-defun matlab-ts-mode--ei-get-new-line (&optional start-node start-offset)
- "Get new line content with element spacing adjusted.
-Optional START-NODE and START-OFFSET are used to compute new pt-offset,
-the point offset in the line used to restore point after updating line.
-Note, new line content may be same as current line. Also computes
-line-node-types which is a string containing the line node types.
-Returns electric indent info, ei-info,
- (list NEW-LINE-CONTENT PT-OFFSET LINE-NODE-TYPES FIRST-NODE-IN-LINE)
-or nil."
- (save-excursion
- (back-to-indentation)
- (when (matlab-ts-mode--no-elements-to-indent)
- (cl-return-from matlab-ts-mode--ei-get-new-line))
-
- ;; Compute ei-line, the electric indented line content
- (let* (pt-offset ;; used in restoring point
- (ei-line (buffer-substring (line-beginning-position) (point)))
- (pair (matlab-ts-mode--move-to-and-get-node))
- (node (or (car pair)
- (cl-return-from matlab-ts-mode--ei-get-new-line)))
- (node-type (cdr pair))
- (first-node node)
- line-node-types
- next2-pair ;; used when we have: (NODE-RE (NEXT-NODE-RE
NEXT2-NODE-RE) N-SPACES-BETWEEN)
- next2-n-spaces-between)
-
- (cl-loop
- while (and (< (point) (line-end-position))
- (< (treesit-node-end node) (line-end-position)))
- do
- (let* ((next-pair (progn
- (goto-char (treesit-node-end node))
- (or next2-pair
- (matlab-ts-mode--move-to-and-get-node))))
- (next-node (let ((candidate-node (car next-pair)))
- (when (not candidate-node)
- (cl-return))
- (when (= (treesit-node-start candidate-node)
- (treesit-node-end candidate-node))
- ;; Syntax errors can result in empty nodes, so
skip this line
- ;; TopTester: electric_indent_empty_node_error.m
- (cl-return-from matlab-ts-mode--ei-get-new-line))
- candidate-node))
- (next-node-type (cdr next-pair))
- (n-spaces-between next2-n-spaces-between))
-
- (setq next2-pair nil
- next2-n-spaces-between nil)
-
- (when matlab-ts-mode--electric-indent-assert
- (setq line-node-types (matlab-ts-mode--update-line-node-types
line-node-types
- node
node-type)))
-
- (when (not n-spaces-between)
- (cl-loop for tuple in matlab-ts-mode--electric-indent-spacing do
- (let* ((node-re (nth 0 tuple))
- (next-spec (nth 1 tuple))
- (next-node-re (if (listp next-spec) (car next-spec)
next-spec))
- (next2-node-re (when (listp next-spec) (cdr
next-spec))))
-
- (when (and (string-match-p node-re node-type)
- (string-match-p next-node-re next-node-type)
- (or (not next2-node-re)
- (save-excursion
- (goto-char (treesit-node-end next-node))
- (let* ((pair
(matlab-ts-mode--move-to-and-get-node))
- (next2-node-type
- (or (cdr pair)
- (cl-return-from
-
matlab-ts-mode--ei-get-new-line))))
-
- (when (string-match-p next2-node-re
next2-node-type)
- (setq next2-pair pair)
- (setq next2-n-spaces-between (nth 2
tuple))
- t)))))
-
- (setq n-spaces-between (nth 2 tuple))
- (when matlab-ts-mode--electric-indent-verbose
- (message "-->ei-matched: %S for node=<\"%s\" %S>
next-node=<\"%s\" %S>"
- tuple node-type node next-node-type
next-node))
- (cl-return)))))
-
- (when (not n-spaces-between)
- (error "Internal error, unhandled node <\"%s\" %S> and next-node
<\"%s\" %S>"
- node-type node next-node-type next-node))
-
- (let* ((node-end (treesit-node-end node))
- (next-node-start (treesit-node-start next-node))
- (extra-chars (matlab-ts-mode--node-extra-chars node-end
next-node-start)))
- ;; Update ei-line
- (when (equal start-node node)
- (setq pt-offset (+ (length ei-line) start-offset)))
-
- (setq ei-line (matlab-ts-mode--concat-ei-line ei-line node
extra-chars n-spaces-between))
-
- (setq node next-node
- node-type next-node-type)
- )))
-
- (when node
- (when (equal start-node node)
- (setq pt-offset (+ (length ei-line) start-offset)))
- (when matlab-ts-mode--electric-indent-assert
- (setq line-node-types (matlab-ts-mode--update-line-node-types
line-node-types
- node
node-type)))
- (let ((extra-chars (matlab-ts-mode--node-extra-chars
- (min (treesit-node-end node) (line-end-position))
- (line-end-position))))
- (setq ei-line (matlab-ts-mode--concat-ei-line ei-line node
extra-chars))))
-
- (list ei-line pt-offset line-node-types first-node))))
-
-(cl-defun matlab-ts-mode--ei-m-matrix-first-col-extra (matrix)
- "For MATRIX indent alignment, get first-col-extra."
- ;; For first-col-extra consider the following where
matlab-ts-mode--array-indent-level is 2.
- ;; In this case first-col-extra will be 1.
- ;; m = [
- ;; 1 2
- ;; 3 4
- ;; ];
- (let ((first-col-extra (save-excursion
- (goto-char (treesit-node-start matrix))
- (forward-char) ;; step over the "["
- (let ((found-element nil))
- ;; found a matrix element?
- (while (and (not found-element)
- (re-search-forward "[^ \t]"
(line-end-position) t))
- (backward-char)
- (let ((node (treesit-node-at (point))))
- (when (not (string-match-p (rx bos (or
"comment"
-
"line_continuation")
- eos)
- (treesit-node-type
node)))
- (setq found-element t))
- (goto-char (min (line-end-position)
- (treesit-node-end node)))))
- (if found-element 0 (1-
matlab-ts-mode--array-indent-level))))))
- first-col-extra))
-
-(cl-defun matlab-ts-mode--ei-m-matrix-col-widths (matrix first-col-extra
&optional first-col-only)
- "Get multi-line MATRIX column widths adding in FIRST-COL-EXTRA to first
column.
-If optional FIRST-COL-ONLY is non-nil, then return only the width of the
-first column in MATRIX.
-Returns alist where each element in the alist is (COLUMN-NUM . WIDTH)"
- (let ((column-widths '())) ;; (alist-get column-num column-widths) ==> width
- (dolist (m-child (treesit-node-children matrix))
- (when (string= (treesit-node-type m-child) "row")
- (let ((column-num 0))
- (cl-loop
- for entry in (treesit-node-children m-child)
- do
- (when (not (string= (treesit-node-type entry) ","))
- (setq column-num (1+ column-num))
- (let ((width (or (alist-get column-num column-widths) 0))) ;;
matrix element width
- (let ((el-width (- (treesit-node-end entry) (treesit-node-start
entry))))
- (when (> el-width width)
- (if (= width 0)
- (push `(,column-num . ,el-width) column-widths)
- (setf (alist-get column-num column-widths) el-width)))))
- (when first-col-only
- (cl-return))
- )))))
-
- (when (> first-col-extra 0)
- (let ((col1-width (+ (alist-get 1 column-widths) first-col-extra)))
- (setf (alist-get 1 column-widths) col1-width)))
-
- column-widths))
-
-(defun matlab-ts-mode--get-m-matrix-row-in-line ()
- "Given point within a matrix assignment statement, return row node.
-Note, nil may be returned when line is only a continuation, e.g.
- v = [1 2; ...
- ...
- 3 4];
-when on the 2nd continuation only line, nil is returned."
- (save-excursion
- (back-to-indentation)
- (let (row-node
- found-ans)
- (cl-loop
- while (not found-ans) do
-
- (let* ((node-at-pt (treesit-node-at (point)))
- (node node-at-pt))
- (while (and node
- (not (string-match-p (rx bos (or "row" "matrix"
"assignment") eos)
- (treesit-node-type node))))
- (setq node (treesit-node-parent node)))
-
- (if (and node
- (string= (treesit-node-type node) "row"))
- (setq found-ans t
- row-node node)
- (goto-char (min (treesit-node-end node-at-pt) (line-end-position)))
- (when (not (re-search-forward "[^ \t]" (line-end-position) t))
- (setq found-ans t)))))
- row-node)))
-
-;; Internal variable that shouldn't be altered. It's used to avoid infinite
recursion.
-(defvar matlab-ts-mode--ei-align-enabled t)
-
-;; This is used to cache matrix alignments for indent-region
-;; It will be non-nil when called from indent-region.
-(defvar matlab-ts-mode--ei-align-matrix-alist nil)
-
-(defun matlab-ts-mode--ei-indent-matrix-in-tmp-buf (assign-node)
- "Insert ASSIGN-NODE in to current tmp-buf and indent.
-Point is left at beginning of line containing the ASSIGN-NODE text."
- (let (assign-str
- n-levels
- start-pt)
- (with-current-buffer (treesit-node-buffer assign-node)
- (let* ((assign-start-pos (save-excursion (goto-char (treesit-node-start
assign-node))
- (line-beginning-position)))
- (assign-end-pos (save-excursion (goto-char (treesit-node-end
assign-node))
- (line-end-position)))
- (indent-spaces (- (treesit-node-start assign-node)
assign-start-pos)))
- (setq assign-str (buffer-substring assign-start-pos assign-end-pos)
- n-levels (if (= (mod indent-spaces matlab-ts-mode--indent-level)
0)
- (/ indent-spaces matlab-ts-mode--indent-level)
- ;; else: not at a standard level so no need to add
conditionals as the
- ;; indent level will be corrected later.
- 0))))
-
- (cl-loop for level from 1 to n-levels do
- (insert "if 1\n"))
-
- (insert assign-str "\n")
-
- (cl-loop for level from 1 to n-levels do
- (insert "end\n"))
-
- (matlab-ts-mode)
-
- ;; Indent to adjust spacing among operators, but don't do other alignment
items
- (let ((matlab-ts-mode--ei-align-enabled nil)
- ;; t-utils-test-indent captures messages using
treesit--indent-verbose and we don't
- ;; want to capture the messages from this temp indent-region.
- (treesit--indent-verbose nil))
- (indent-region (point-min) (point-max)))
-
-
- (goto-char (point-min))
- (when (> n-levels 0)
- (forward-line n-levels))))
-
-(cl-defun matlab-ts-mode--ei-align-line-in-m-matrix (assign-node ei-info)
- "Align current line with EI-INFO in a multi-line matrix of ASSIGN-NODE.
-See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
-
- ;; TopTester:
test-matlab-ts-mode-electric-indent-files/electric_indent_matrix_cols.m
- (when matlab-ts-mode--ei-align-matrix-alist ;; Use cached value?
- (let ((ei-line (alist-get (line-number-at-pos)
matlab-ts-mode--ei-align-matrix-alist)))
- (when ei-line
- (cl-return-from matlab-ts-mode--ei-align-line-in-m-matrix (cons
ei-line (cdr ei-info))))))
-
- (let* ((assign-start-linenum (line-number-at-pos (treesit-node-start
assign-node)))
- (tmp-buf-ei-linenum (1+ (- (line-number-at-pos)
assign-start-linenum)))
- (tmp-buf-row-linenum (if matlab-ts-mode--ei-align-matrix-alist 1
tmp-buf-ei-linenum))
- (matrix-alist matlab-ts-mode--ei-align-matrix-alist))
- (with-temp-buffer
- (matlab-ts-mode--ei-indent-matrix-in-tmp-buf assign-node)
-
- (let* ((matrix-node (treesit-node-parent (treesit-search-subtree
- (treesit-buffer-root-node) (rx
bos "[" eos) nil t)))
- (first-col-extra (matlab-ts-mode--ei-m-matrix-first-col-extra
matrix-node))
- (column-widths (matlab-ts-mode--ei-m-matrix-col-widths
matrix-node first-col-extra)))
-
- ;; Move to the line of interest when we called from
matlab-ts-mode--treesit-indent,
- ;; otherwise calculate all matrix rows for indent-region.
- (when (and (not matrix-alist)
- (> tmp-buf-ei-linenum 1))
- (forward-line (1- tmp-buf-ei-linenum)))
-
- (while (not (eobp)) ;; Adjust column widths
- (back-to-indentation)
- (let* ((row-node (matlab-ts-mode--get-m-matrix-row-in-line))
- (indent-start-pt (point))
- ;; line content does not have leading indent-level spaces
- (content (buffer-substring indent-start-pt
(line-end-position)))
- (ei-line (buffer-substring (line-beginning-position)
(line-end-position)))
- n-spaces)
- (when row-node
- (let* ((col-num (length column-widths))
- (pt-offset (nth 1 ei-info))
- (matrix-offset (save-excursion
- (goto-char (treesit-node-start
matrix-node))
- (1+ (- (point)
(line-beginning-position)))))
- (indent-offset (or (string-match-p "[^ \t]+" ei-line)
- (error "Assert: no offset"))))
-
- (when (< matrix-offset indent-offset)
- (when pt-offset
- (setq pt-offset (- pt-offset (- indent-offset
matrix-offset))))
- (setq indent-offset matrix-offset))
-
- (dolist (element (reverse (treesit-node-children row-node)))
- (when (not (string= (treesit-node-type element) ",")) ;; at
a column?
- (let ((width (or (alist-get col-num column-widths)
- (error "Assert: no col width")))
- (curr-width (- (treesit-node-end element)
(treesit-node-start element))))
- (setq n-spaces (- width curr-width))
- (when (< curr-width width)
- (let ((offset (- (treesit-node-start element)
indent-start-pt)))
- (when (and pt-offset
- (or (= indent-start-pt (point-min))
- (<= (+ offset matrix-offset)
pt-offset)))
- (setq pt-offset (+ pt-offset n-spaces)))
- (setq content (concat (substring content 0 offset)
- (make-string n-spaces ? )
- (substring content offset)))))
- (setq col-num (1- col-num)))))
-
- (setq ei-line (concat (substring ei-line 0 indent-offset)
content))
- (when (= tmp-buf-row-linenum tmp-buf-ei-linenum)
- (setq ei-info (list ei-line pt-offset (nth 2 ei-info) (nth 3
ei-info))))))
-
- (when matrix-alist
- (let* ((buf-linenum (1- (+ assign-start-linenum
tmp-buf-row-linenum))))
- (push `(,buf-linenum . ,ei-line) matrix-alist)))
-
- (if (not matrix-alist)
- (goto-char (point-max))
- (forward-line)
- (setq tmp-buf-row-linenum (1+ tmp-buf-row-linenum)))))))
-
- (when matrix-alist
- (setq matlab-ts-mode--ei-align-matrix-alist matrix-alist)))
- ;; ei-info for current line
- ei-info)
-
-(cl-defun matlab-ts-mode--ei-matrix-ends-on-line (matrix)
- "Does MATRIX end on a line by itself?"
- (save-excursion
- (goto-char (treesit-node-end matrix))
- (while (re-search-forward "[^ \t]" (line-end-position) t)
- (backward-char)
- (let ((node (treesit-node-at (point))))
- (when (not (string-match-p (rx bos (or "," ";" "comment"
"line_continuation") eos)
- (treesit-node-type node)))
- (cl-return-from matlab-ts-mode--ei-matrix-ends-on-line))
- (goto-char (treesit-node-end node)))))
- t)
-
-(cl-defun matlab-ts-mode--ei-is-m-matrix (matrix)
- "Is MATRIX node a multi-line matrix?
-We define a a multi-line matrix has one row per line and more than one
-column."
- (let ((start-line (line-number-at-pos (treesit-node-start matrix)))
- (end-line (line-number-at-pos (treesit-node-end matrix)))
- (n-rows 0)
- n-cols)
- (when (and (> end-line start-line) ;; multi-line matrix?
- (matlab-ts-mode--ei-matrix-ends-on-line matrix)
- (not (treesit-search-subtree matrix (rx bos "ERROR" eos) nil
t)))
- (dolist (child (treesit-node-children matrix))
- (let ((child-type (treesit-node-type child)))
- (cond
- ((string= child-type "row")
- (let ((row-start-line-num (line-number-at-pos (treesit-node-start
child))))
- ;; Return nil if row not on one line
- (when (not (= row-start-line-num (line-number-at-pos
(treesit-node-end child))))
- (cl-return-from matlab-ts-mode--ei-is-m-matrix))
- ;; Return nil if more than one row one the line
- (let ((next-node (treesit-node-next-sibling child)))
- (when (and (string= (treesit-node-type next-node) "row")
- (= row-start-line-num (line-number-at-pos
- (treesit-node-start
next-node))))
- (cl-return-from matlab-ts-mode--ei-is-m-matrix)))
- ;; Return nil if row contains sub-matrices
- (when (treesit-search-subtree child (rx bos (or "[" "]") eos)
nil t)
- (cl-return-from matlab-ts-mode--ei-is-m-matrix))
-
- (setq n-rows (1+ n-rows))
-
- ;; Count the columns
- (let ((n-cols-in-row 0))
- (dolist (el (treesit-node-children child))
- (when (not (string= (treesit-node-type el) ","))
- (setq n-cols-in-row (1+ n-cols-in-row))))
- (if (not n-cols)
- (setq n-cols n-cols-in-row)
- (if (not (= n-cols n-cols-in-row))
- (cl-return-from matlab-ts-mode--ei-is-m-matrix))))
- ))
- ;; Case unexpected matrix child node
- ((not (string-match-p (rx bos (or "[" "]" "comment"
"line_continuation") eos)
- child-type))
- (error "Assert: unexpected matrix child %S" child))))))
- ;; Matrix with more than one row and more than one column where each row
is on its own line?
- (and (> n-rows 1) (>= n-cols 1))))
-
-(defun matlab-ts-mode--ei-is-assign (first-node type)
- "Is FIRST-NODE of line for an assignment that matches TYPE?
-TYPE can be \\='single-line or \\='multi-line-matrix. When
-\\='multi-line-matrix, the assignment is to a matrix with two or more
-rows and two or more columns, where each row is on its own line. The
-assignment node is return or nil."
- (let ((assign-node (treesit-node-parent first-node)))
- (while (and assign-node
- (not (string= (treesit-node-type assign-node) "assignment")))
- (setq assign-node (treesit-node-parent assign-node)))
- (when assign-node
- (save-excursion
- (beginning-of-line)
- (when (re-search-forward "=" (line-end-position) t)
- (backward-char)
- (let ((eq-node (treesit-node-at (point))))
- ;; First "=" must be an assignment (assumptions elsewhere require
this).
- (when (and (equal (treesit-node-type eq-node) "=")
- (equal (treesit-node-type (treesit-node-parent
eq-node)) "assignment"))
- (cond
- ;; Single-line assignment? Example: v1 = [1, 2];
- ((eq type 'single-line)
- (when (<= (treesit-node-end assign-node) (line-end-position))
- assign-node))
-
- ;; Multi-line matrix assignment? Example: m1 = [1 2
- ;; 3 4];
- ((eq type 'multi-line-matrix)
- (goto-char (treesit-node-end eq-node))
- (let ((next-node (treesit-node-next-sibling eq-node)))
- (while (equal (treesit-node-type next-node)
"line_continuation")
- (setq next-node (treesit-node-next-sibling next-node)))
- (when (and (equal (treesit-node-type next-node) "matrix")
- (matlab-ts-mode--ei-is-m-matrix next-node))
- assign-node)))
- (t
- (error "Assert: bad type %S" type))))))))))
-
-(defun matlab-ts-mode--ei-point-in-m-matrix (ei-info)
- "Are we in a multi-line matrix?
-See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents. Returns
-mat-info a (list matrix-node n-rows n-cols) if in a multi-line matrix."
- (let* ((first-node-in-line (nth 3 ei-info))
- (parent (treesit-node-parent first-node-in-line)))
- (while (and parent
- (not (string= (treesit-node-type parent) "assignment")))
- (setq parent (treesit-node-parent parent)))
- (when parent ;; In an assignment?
- (save-excursion
- (goto-char (treesit-node-start parent))
- (beginning-of-line)
- (back-to-indentation)
- (let ((first-node (treesit-node-at (point))))
- (matlab-ts-mode--ei-is-assign first-node 'multi-line-matrix))))))
-
-(defun matlab-ts-mode--ei-assign-offset (ei-line)
- "Get the assignment offset from the indent-level in EI-LINE."
- (let* ((first-char-offset (or (string-match-p "[^ \t]" ei-line) (error
"Assert: no first char")))
- (offset (- (or (string-match-p "=" ei-line) (error "Assert: no ="))
- first-char-offset)))
- offset))
-
-;; This is used to cache aligned assignments for indent-region
-(defvar-local matlab-ts-mode--ei-align-assign-alist nil)
-
-(defun matlab-ts-mode--ei-align-assignments (ei-info)
- "Update EI-INFO to align assignments.
-See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
- (let ((first-node-in-line (nth 3 ei-info)))
- (when (matlab-ts-mode--ei-is-assign first-node-in-line 'single-line)
- (let* ((ei-line (nth 0 ei-info))
- (line-assign-offset (matlab-ts-mode--ei-assign-offset ei-line))
- assign-offset
- line-nums
- line-start-pt)
-
- (when (or (not matlab-ts-mode--ei-align-assign-alist)
- (not (setq assign-offset (alist-get (line-number-at-pos)
-
matlab-ts-mode--ei-align-assign-alist))))
- (setq assign-offset line-assign-offset)
- (setq line-nums `(,(line-number-at-pos)))
- (save-excursion
- (beginning-of-line)
- (setq line-start-pt (point))
-
- ;; Look backwards and then forwards for single-line assignments
- (cl-loop
- for direction in '(-1 1) do
- (goto-char line-start-pt)
- (cl-loop
- while (not (if (= direction -1) (bobp) (eobp))) do
- (forward-line direction)
- (let* ((l-info (matlab-ts-mode--ei-get-new-line))
- (l-first-node (nth 3 l-info)))
- (if (and l-first-node
- (matlab-ts-mode--ei-is-assign l-first-node
'single-line))
- (let ((l-offset (matlab-ts-mode--ei-assign-offset (nth 0
l-info))))
- (push (line-number-at-pos) line-nums)
- (when (> l-offset assign-offset)
- (setq assign-offset l-offset)))
- (cl-return))))))
- (when matlab-ts-mode--ei-align-assign-alist
- (dolist (line-num line-nums)
- (push `(,line-num . ,assign-offset)
matlab-ts-mode--ei-align-assign-alist))))
-
- (let ((diff (- assign-offset line-assign-offset)))
- (when (> diff 0)
- (let* ((loc (1- (string-match "=" ei-line)))
- (new-pt-offset (let ((pt-offset (nth 1 ei-info)))
- (when pt-offset
- (if (<= loc pt-offset)
- (+ pt-offset diff)
- pt-offset)))))
- (setq ei-line (concat (substring ei-line 0 loc)
- (make-string diff ? )
- (substring ei-line loc)))
- (setq ei-info (list ei-line new-pt-offset (nth 2 ei-info) (nth 3
ei-info)))))))))
- ei-info)
-
-(defun matlab-ts-mode--ei-trailing-comment-offset (ei-info)
- "Get trailing comment offset from first char current line?
-See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents.
-To simplify implementation, we require that the first \"%\" character in
-the line be the start of the trailing comment. Thus,
- s = \"foo % bar\" % comment
-is not identified as a trailing comment and
- s = \"foo bar\" %comment
-is identified as a trailing comment."
- (when ei-info
- (save-excursion
- (beginning-of-line)
- (when (re-search-forward "%" (line-end-position) t)
- (let ((node (treesit-node-at (point))))
- (when (equal (treesit-node-type node) "comment")
- (let* ((new-line (nth 0 ei-info))
- (offset (- (string-match-p "%" new-line) (string-match-p
"[^ \t]" new-line))))
- (when (> offset 0)
- ;; have trailing comment at offset from first char in new-line
- offset))))))))
-
-;; This is used to cache comment alignments for indent-region
-(defvar-local matlab-ts-mode--ei-align-comment-alist nil)
-
-(defun matlab-ts-mode--ei-align-trailing-comments (ei-info)
- "Align trailing comments in EI-INFO.
-See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
- (let* ((line-comment-offset (matlab-ts-mode--ei-trailing-comment-offset
ei-info)))
- (when line-comment-offset
- (let* (comment-offset
- line-nums
- line-start-pt)
-
- (when (or (not matlab-ts-mode--ei-align-comment-alist)
- (not (setq comment-offset (alist-get (line-number-at-pos)
-
matlab-ts-mode--ei-align-comment-alist))))
- (setq comment-offset line-comment-offset)
- (setq line-nums `(,(line-number-at-pos)))
- (save-excursion
- (beginning-of-line)
- (setq line-start-pt (point))
-
- ;; Look backwards and then forwards for lines with trailing
comments
- (cl-loop
- for direction in '(-1 1) do
- (goto-char line-start-pt)
- (cl-loop
- while (not (if (= direction -1) (bobp) (eobp))) do
- (forward-line direction)
- (let* ((ei-l-info (matlab-ts-mode--ei-get-new-line))
- (l-offset (matlab-ts-mode--ei-trailing-comment-offset
ei-l-info)))
- (if l-offset
- (progn
- (push (line-number-at-pos) line-nums)
- (when (> l-offset comment-offset)
- (setq comment-offset l-offset)))
- (cl-return))))))
-
- (when matlab-ts-mode--ei-align-comment-alist
- (dolist (line-num line-nums)
- (push `(,line-num . ,comment-offset)
matlab-ts-mode--ei-align-comment-alist))))
-
- (let ((diff (- comment-offset line-comment-offset)))
- (when (> diff 0)
- (let* ((ei-line (nth 0 ei-info))
- (loc (1- (string-match "%" ei-line)))
- (new-pt-offset (let ((pt-offset (nth 1 ei-info)))
- (when pt-offset
- (if (<= loc pt-offset)
- (+ pt-offset diff)
- pt-offset)))))
- (setq ei-line (concat (substring ei-line 0 loc)
- (make-string diff ? )
- (substring ei-line loc)))
- (setq ei-info (list ei-line new-pt-offset (nth 2 ei-info) (nth 3
ei-info))))))))
- ei-info))
-
-(defun matlab-ts-mode--ei-align (ei-info)
- "Align elements in EI-INFO.
-See `matlab-ts-mode--ei-get-new-line' for EI-INFO contents."
- (let ((matrix-node (matlab-ts-mode--ei-point-in-m-matrix ei-info)))
- (if matrix-node
- (setq ei-info (matlab-ts-mode--ei-align-line-in-m-matrix matrix-node
ei-info)))
- ;; else do single-line alignments
- (setq ei-info (matlab-ts-mode--ei-align-assignments ei-info))
- (setq ei-info (matlab-ts-mode--ei-align-trailing-comments ei-info)))
- ei-info)
-
-(cl-defun matlab-ts-mode--indent-elements-in-line (&optional start-node
start-offset)
- "Indent current line by adjust spacing around elements.
-Optional START-NODE and START-OFFSET are used to restore the point when
-line is updated. Returns t if line was updated."
-
- ;; If line was indented (ei-line is not same as current line), then update
the buffer
- (let ((ei-info (matlab-ts-mode--ei-get-new-line start-node start-offset)))
- (when ei-info
- (when matlab-ts-mode--ei-align-enabled
- (setq ei-info (matlab-ts-mode--ei-align ei-info)))
- (let* ((ei-line (nth 0 ei-info))
- (pt-offset (nth 1 ei-info))
- (line-node-types (nth 2 ei-info))
- (curr-line (buffer-substring (line-beginning-position)
(line-end-position)))
- (updated (not (string= curr-line ei-line))))
-
- (when updated
- (delete-region (line-beginning-position) (line-end-position))
- (insert ei-line)
- (when matlab-ts-mode--electric-indent-assert
- (matlab-ts-mode--electric-indent-assert-match line-node-types))
- (when pt-offset
- (goto-char (+ (line-beginning-position) pt-offset))))
- ;; result
- updated))))
-
(defun matlab-ts-mode--treesit-indent ()
"Call `treesit-indent', then do electric indent."
(treesit-indent)
@@ -3878,16 +2881,15 @@ line is updated. Returns t if line was updated."
(setq start-node node)
(setq start-offset (- (point) (treesit-node-start node)))))))
- (if (matlab-ts-mode--indent-elements-in-line start-node start-offset)
+ (if (matlab-ts-mode--ei-indent-elements-in-line start-node start-offset)
(when at-eol
(end-of-line))
(goto-char start-pt)))))
-
(defun matlab-ts-mode--treesit-indent-region (beg end)
"Call `treesit-indent-region' on BEG END, then do electric indent."
;; `treesit-indent-region' will not alter the number of lines, but it may
reduce the buffer size,
- ;; thus grab the start/end lines for
`matlab-ts-mode--indent-elements-in-line'.
+ ;; thus grab the start/end lines for
`matlab-ts-mode--ei-indent-elements-in-line'.
(if matlab-ts-mode-electric-indent
(let* ((start-linenum (line-number-at-pos beg))
(curr-linenum start-linenum)
@@ -3919,7 +2921,7 @@ line is updated. Returns t if line was updated."
(goto-char beg)
(while (<= curr-linenum end-linenum)
(beginning-of-line)
- (matlab-ts-mode--indent-elements-in-line)
+ (matlab-ts-mode--ei-indent-elements-in-line)
(forward-line)
(setq curr-linenum (1+ curr-linenum)))
;; Restore point accounting for whitespace adjustments in the
lines