From c12e9bcc65f30f2a3c212d0ab8fb35fb71d9d718 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Feb 2021 21:10:20 -0500 Subject: [PATCH 001/168] matlab.el: Support some block keywords like 'arguments' to be used as a variable or fnc name. (matlab-keyword-list): Remove MCOS keywords. (matlab-keyword-first-on-line-list): New, is MCOS keywords from a bove. (matlab-font-lock-keywords): Show first-on-line keywords only if first on a line. (matlab-mcos-innerblock-regexp): New (matlab-mcos-regexp): Now uses above, but add classdef. (matlab-block-syntax-re, matlab-innerblock-syntax-re): New (matlab-block-start-scan-re): New (matlab-backward-sexp): When block start found, use `-on-valid-block-start' in addition to '-in-string-or-comment'. (matlab-forward-sexp): Same check when block start is found. Also new input 'parentblock' can be used to speed up valid block start check. Use the new input when recursing. Use 'matlab-block-start-scan-re' instead of building local regexp. (matlab-valid-block-start-slow-and-careful): New setting (matlab-cursor-on-valid-block-start): New. (matlab-current-syntactic-block): New (matlab-lattr-block-cont): Make sure blocks found are valid. (matlab-show-paren-or-block): Make sure blocks found are valid. Use new block-re fcns for regexp. --- matlab.el | 191 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 171 insertions(+), 20 deletions(-) diff --git a/matlab.el b/matlab.el index 374e9a0..809efa7 100644 --- a/matlab.el +++ b/matlab.el @@ -1065,15 +1065,20 @@ Argument LIMIT is the maximum distance to search." "return" "break" "continue" "switch" "case" "otherwise" "try" "catch" "tic" "toc" - ;; MCOS keywords - "properties" "methods" "enumeration" "events" - "arguments" ) "List of keywords for MATLAB used in highlighting. Customizing this variable is only useful if `regexp-opt' is available." :group 'matlab :type '(repeat (string :tag "Keyword: "))) +(defcustom matlab-keyword-first-on-line-list '( "properties" "methods" "enumeration" "events" + "arguments" + ) + "List of keywords for MATLAB that should be highilghted only if the first word on a line. +Customizing this variable is only useful if `regexp-opt' is available." + :group 'matlab + :type '(repeat (string :tag "Keyword: "))) + (defcustom matlab-handle-graphics-list '("figure" "axes" "axis" "line" "surface" "patch" "text" "light" "image" "set" "get" "uicontrol" @@ -1136,6 +1141,11 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." ;; General keywords (list (matlab-font-lock-regexp-opt matlab-keyword-list) '(0 font-lock-keyword-face)) + ;; Keywords that should be the first word on a line + (list (concat "^\\s-*\\(" + (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) + "\\)") + '(1 font-lock-keyword-face)) ;; The end keyword is only a keyword when not used as an array ;; dereferencing part. '("\\(^\\|[;,]\\)[ \t]*\\(end\\)\\b" @@ -1739,7 +1749,12 @@ Return nil if it is being used to dereference an array." (defconst matlab-defun-regex "^\\(\\s-*function\\|classdef\\)[ \t.[]" "Regular expression defining the beginning of a MATLAB function.") -(defconst matlab-mcos-regexp "\\|classdef\\|properties\\|methods\\|events\\|enumeration\\|arguments" +(defconst matlab-mcos-innerblock-regexp "\\|properties\\|methods\\|events\\|enumeration\\|arguments" + "Keywords which mark the beginning of mcos blocks. +These keywords can be overriden as variables or functions in other contexts +asside from that which they declare their content.") + +(defconst matlab-mcos-regexp (concat "\\|classdef" matlab-mcos-innerblock-regexp) "Keywords which mark the beginning of mcos blocks.") (defcustom matlab-block-indent-tic-toc-flag nil @@ -1748,6 +1763,16 @@ This variable should be set before loading matlab.el" :group 'matlab :type 'boolean) +(defconst matlab-block-syntax-re + (concat "\\(function" matlab-mcos-regexp "\\)\\>") + "Keywords that represent blocks that have custom internal syntax. +Used by `matlab-cursor-on-valid-block-start'.") + +(defconst matlab-innerblock-syntax-re + (concat "\\(function" matlab-mcos-innerblock-regexp "\\)\\>") + "Keywords that represent blocks that have custom internal syntax. +Used by `matlab-cursor-on-valid-block-start'.") + (defconst matlab-block-beg-pre-if (if matlab-block-indent-tic-toc-flag (concat "function\\|parfor\\|spmd\\|for\\|while\\|if\\|switch\\|try\\|tic" @@ -1814,6 +1839,13 @@ blocks.") (matlab-block-end-pre) "\\|" matlab-endless-blocks "\\)\\b")) +(defun matlab-block-start-scan-re () + "Expression used to scan over matching pairs of begin/ends. +Assume cursor is on the beginning of the upcoming word." + (concat "\\(" + (matlab-block-beg-pre) "\\|" + matlab-block-mid-pre "\\)\\b")) + (defun matlab-block-scan-re () "Expression used to scan over matching pairs of begin/ends." (concat "\\(^\\|[;,]\\)\\s-*\\(" @@ -2062,7 +2094,10 @@ This assumes that expressions do not cross \"function\" at the left margin." (unless (matlab-backward-sexp nil noerror) (setq done t returnme nil))) - (if (not (matlab-cursor-in-string-or-comment)) + ;; Make sure we are at a valid block construct and not + ;; comments or other weird spot. + (if (and (not (matlab-cursor-in-string-or-comment)) + (matlab-cursor-on-valid-block-start)) (setq done t)))) (goto-char start) (if noerror @@ -2071,11 +2106,13 @@ This assumes that expressions do not cross \"function\" at the left margin." (error "Unstarted END construct")))) returnme))))) -(defun matlab-forward-sexp (&optional includeelse autostart) +(defun matlab-forward-sexp (&optional includeelse autostart parentblock) "Go forward one balanced set of MATLAB expressions. Optional argument INCLUDEELSE will stop on ELSE if it matches the starting IF. If AUTOSTART is non-nil, assume we are already inside a block, and navigate -forward until we exit that block." +forward until we exit that block. +PARENTBLOCK is used when recursing to validate block starts as being in +a valid context." (interactive "P") (let (p) ;; go to here if no error. (save-excursion ;; don't go anywhere if there is an error @@ -2092,11 +2129,8 @@ forward until we exit that block." ) ;; No autostart, and looking at a block keyword. ((and (not autostart) - (or (not (looking-at (concat "\\(" - (matlab-block-beg-pre) - "\\|" - (matlab-block-mid-re) - "\\)\\>"))) + (or (not (looking-at (matlab-block-start-scan-re))) + ;; ^^ (concat "\\(" (matlab-block-beg-pre) "\\|" (matlab-block-mid-re) "\\)\\>") (matlab-cursor-in-string-or-comment))) ;; Go forwards one simple expression (matlab-move-simple-sexp-internal 1)) @@ -2107,8 +2141,11 @@ forward until we exit that block." ;; Default behavior. (t - ;; Not autostart, skip next word. - (unless autostart (forward-word 1)) + ;; Not autostart, skip next word, and also track what it is + ;; for nesting purposes. + (unless autostart + (setq parentblock (buffer-substring-no-properties + (point) (progn (forward-word 1) (point))))) (let ((done nil) (s nil) (expr-scan (if includeelse (matlab-block-re) @@ -2120,11 +2157,18 @@ forward until we exit that block." (pos-visible-in-window-p))) (goto-char (match-beginning 2)) (if (looking-at expr-look) - (if (matlab-cursor-in-string-or-comment) + (if (or (matlab-cursor-in-string-or-comment) + ;; We'd like to do this: + ;;(not (matlab-cursor-on-valid-block-start)) + ;; but it travels backwards. Instead, we need to track the + ;; last block we are diving down from and just use that + ;; instead of looking it up along the way. + (not (matlab-cursor-on-valid-block-start parentblock)) + ) (forward-word 1) ;; we must skip the expression and keep searching ;; NEVER EVER call with value of INCLUDEELSE - (matlab-forward-sexp)) + (matlab-forward-sexp nil nil (match-string-no-properties 1))) (forward-word 1) (if (and (not (matlab-cursor-in-string-or-comment)) (matlab-valid-end-construct-p)) @@ -2134,6 +2178,106 @@ forward until we exit that block." (setq p (point)))) ;; really go here (goto-char p))) +(defvar matlab-valid-block-start-slow-and-careful t + "Be very careful with determining of a block is valid when t. +Set to nil if fast-and-loose is ok.") + +(defun matlab-cursor-on-valid-block-start (&optional known-parent-block) + "Return t if cursor is on a valid block start. +Valid block starts are those that represent a syntax context, like function, +classdef, properties, etc. +KNOWN-PARENT-BLOCK is a string that represents the context cursor is in. +Use this if you know what context you're in." + (save-match-data + (save-restriction + (widen) + (cond + ((not (looking-at (matlab-block-beg-re))) + ;; Not looking at a valid block + nil) + ;; Else, are we on a block that has special syntax? + ((not (looking-at matlab-innerblock-syntax-re)) + ;; Not an innerblock syntax that only work withing special blocks + ;; thus automatically true. + t) + + ;; Else, a special block. We need to check the context of this + ;; block to know if this innerblock is valid. + + ;; Cheap check - if functions don't have end, then always invalid + ;; since these context blocks can't exist. + ((not matlab-functions-have-end) + t) + + ;; Cheap check - is this block keyword not the first word on the line? + ((save-excursion (skip-syntax-backward " ") (not (bolp))) + ;; Not first on line, not valid block. + ;; Technically it COULD be valid, but we need some cheap ways + ;; to skip over some types of syntaxes that look dumb. + nil) + + ;; Expensive check - is this block in the right context? + ((or matlab-valid-block-start-slow-and-careful known-parent-block) + + (let ((foundblock (match-string-no-properties 1)) + (myblock (if known-parent-block + (cons known-parent-block nil) ;; shortcut if known + (matlab-current-syntactic-block)))) + (cond ((and (string= foundblock "arguments") + (string= (car myblock) "function")) + ;; We found correct usage of arguments. + t) + ((and (string= foundblock "function") + (or (not (car myblock)) + (string= (car myblock) "methods"))) + ;; We found correct usage of function. + t) + ((string= (car myblock) "classdef") + ;; We found correct usage of methods, events, etc. + t) + (t + ;; Some other case is bad. + nil))) + ) + + ;; A cheap version of the expensive check + ((and (not matlab-valid-block-start-slow-and-careful) + (looking-at matlab-innerblock-syntax-re)) + ;; If we are not slow and careful, we just need to return t if we see + ;; of of these keywords since these were filtered out earlier. + t) + + ;; If none of the valid cases, must be invalid + (t nil) + )))) + +(defun matlab-current-syntactic-block () + "Return information about the current syntactic block the cursor is in. +Value returned is of the form ( BLOCK-TYPE . PT ) where BLOCK-TYPE is a +string, such as 'function' or 'properties', and PT is the location that +the block starts at. + +This function skips over blocks such as 'switch' and 'if', and only returns +blocks that change the syntax of their contents, such as: + function, classdef, properties, events, methods, arguments +" + (let ((block-type nil) + (block-beg nil)) + + (save-excursion + ;; By specifying NOERROR, returns nil if we can't move + ;; backward, which means we should stop. + + ;; backward sexp also knows to skip invlaid block starts, so we'll + ;; only land on safe blocks. + (when (and (matlab-backward-sexp t t) + (looking-at matlab-block-syntax-re)) + (setq block-type (match-string-no-properties 1) + block-beg (point))) + ) + + (cons block-type block-beg))) + (defun matlab-indent-sexp () "Indent the syntactic block starting at point." (interactive) @@ -2506,8 +2650,11 @@ Optional EOL indicates a virtual end of line." (matlab-navigation-syntax (while (re-search-forward (concat "\\<" (matlab-block-beg-re) "\\>") nil t) - (if (matlab-cursor-in-string-or-comment) - ;; Do nothing + (if (or (matlab-cursor-in-string-or-comment) + (not (save-excursion (forward-word -1) + (matlab-cursor-on-valid-block-start)))) + ;; Do nothing if in comment, or if the thing we skipped over was + ;; an invalid block construct (based on local context) nil ;; Increment counter, move to end. (setq v (1+ v)) @@ -3876,7 +4023,7 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" (t ;; Part 2: Are we looking at a block start/end, such as if end; - ;; If we are on On a word character, or just after a + ;; If we are on a word character, or just after a ;; word character move back one symbol. This will let ;; us use the block begin / end matchers to figure ;; out where we are. @@ -3899,7 +4046,11 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" there-end (match-end 0) mismatch nil) ) - ((looking-at (concat (matlab-block-beg-re) "\\>")) + ((matlab-cursor-on-valid-block-start) + ;; Above is similar to vvv but with special checks. + ;; Still need to do vvv b/c we need the match-data. + (looking-at (concat (matlab-block-beg-re) "\\>")) + ;; We are at the beginning of a block. Navigate forward to the end ;; statement. (setq here-beg (match-beginning 0) From d1cf00a95ad43e95698bfdfef4a048d8608adf8c Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Feb 2021 21:10:48 -0500 Subject: [PATCH 002/168] tests/blocks.m New file with lots of examples of strange uses of block syntax. --- tests/blocks.m | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/blocks.m diff --git a/tests/blocks.m b/tests/blocks.m new file mode 100644 index 0000000..4bf150d --- /dev/null +++ b/tests/blocks.m @@ -0,0 +1,80 @@ +classdef blocks < handle +% !!0 + + properties + normalprop = 1; + end + + properties(Access='public') + % See if we can create properties using keywords + %properties = 1; + %methods = 1; + %events = 1; + arguments + prop = 1; + end + + events (Access='private') + %properties + %events + %methods + arguments + misc + end + + methods + + function simple_method(obj) + arguments + obj + end + + disp(obj.normalprop); + end + + function obj = blocks(arguments,events,properties,methods,enumeration,normal) + + arguments + arguments + events + properties + methods + enumeration + normal + end + + obj.prop = arguments; + obj.prop = events; + obj.prop = properties; + obj.prop = methods; + obj.prop = enumeration; + obj.prop = normal; + end + + function properties(~) + end + + function methods(~) + end + + function events(~) + end + + function arguments(~) + end + + function enumeration(~) + end + + function usestuff(obj) + % Try using the methods of this object + obj.properties(); + obj.methods(); + obj.events(); + obj.arguments(); + obj.enumeration(); + end + + end + +end \ No newline at end of file From 0c9226c430e86d6143f0eb49759f3a0602cd6796 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Feb 2021 21:20:58 -0500 Subject: [PATCH 003/168] matlab.el: (matlab-mcos-innerblock-regexp, matlab-mcos-regexp): Tweaks to support changing ... (matlab-innerblock-syntax-re): Remove fcn from this regexp. (matlab-cursor-on-valid-block-start): Don't do special validation for 'function'. --- matlab.el | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/matlab.el b/matlab.el index 809efa7..fc2823e 100644 --- a/matlab.el +++ b/matlab.el @@ -1749,12 +1749,12 @@ Return nil if it is being used to dereference an array." (defconst matlab-defun-regex "^\\(\\s-*function\\|classdef\\)[ \t.[]" "Regular expression defining the beginning of a MATLAB function.") -(defconst matlab-mcos-innerblock-regexp "\\|properties\\|methods\\|events\\|enumeration\\|arguments" +(defconst matlab-mcos-innerblock-regexp "properties\\|methods\\|events\\|enumeration\\|arguments" "Keywords which mark the beginning of mcos blocks. These keywords can be overriden as variables or functions in other contexts asside from that which they declare their content.") -(defconst matlab-mcos-regexp (concat "\\|classdef" matlab-mcos-innerblock-regexp) +(defconst matlab-mcos-regexp (concat "\\|classdef\\|" matlab-mcos-innerblock-regexp) "Keywords which mark the beginning of mcos blocks.") (defcustom matlab-block-indent-tic-toc-flag nil @@ -1769,7 +1769,7 @@ This variable should be set before loading matlab.el" Used by `matlab-cursor-on-valid-block-start'.") (defconst matlab-innerblock-syntax-re - (concat "\\(function" matlab-mcos-innerblock-regexp "\\)\\>") + (concat "\\(" matlab-mcos-innerblock-regexp "\\)\\>") "Keywords that represent blocks that have custom internal syntax. Used by `matlab-cursor-on-valid-block-start'.") @@ -2227,11 +2227,6 @@ Use this if you know what context you're in." (string= (car myblock) "function")) ;; We found correct usage of arguments. t) - ((and (string= foundblock "function") - (or (not (car myblock)) - (string= (car myblock) "methods"))) - ;; We found correct usage of function. - t) ((string= (car myblock) "classdef") ;; We found correct usage of methods, events, etc. t) From 6dbdf778f98c85ce772fca1f0d737a895f774dfd Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Feb 2021 21:47:12 -0500 Subject: [PATCH 004/168] matlab.el: (matlab-function-font-lock-keywords): Support functions with names that are prepended with set. or get. which are valid set/get methods. --- matlab.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/matlab.el b/matlab.el index fc2823e..41efa62 100644 --- a/matlab.el +++ b/matlab.el @@ -1199,22 +1199,22 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." ;; function name, and an optional (possibly empty) list of input variables (list (concat "^\\s-*\\(function\\)\\>[ \t\n.]*" "\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*" - "=[ \t\n.]*\\(\\sw+\\)[ \t\n.]*" + "=[ \t\n.]*\\(\\(?:[sg]et\\.\\)?\\sw+\\)[ \t\n.]*" matlab-function-arguments) '(1 font-lock-keyword-face append) '(2 font-lock-variable-name-face append) - '(3 font-lock-function-name-face append)) + '(3 font-lock-function-name-face prepend)) ;; defining a function, a function name, and an optional (possibly ;; empty) list of input variables (list (concat "^\\s-*\\(function\\)[ \t\n.]+" - "\\(\\sw+\\)[ \t\n.]*" + "\\(\\(?:[sg]et\\.\\)?\\sw+\\)[ \t\n.]*" matlab-function-arguments) '(1 font-lock-keyword-face append) - '(2 font-lock-function-name-face append)) + '(2 font-lock-function-name-face prepend)) ;; Anchor on the function keyword, highlight params (list (concat "^\\s-*function\\>[ \t\n.]*" "\\(\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*=[ \t\n.]*\\)?" - "\\sw+\\s-*(") + "\\(?:[sg]et\\.\\)?\\sw+\\s-*(") '("\\s-*\\(\\sw+\\)\\s-*[,)]" (save-excursion (matlab-end-of-command) (point)) nil From 3fd09f43f5b7772dea7892088cfa58c5ac498a43 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Feb 2021 11:41:29 -0500 Subject: [PATCH 005/168] Separate font-lock keywords that are only for M files, and those that work fine for files and the shell. matlab.el: (matlab*-font-lock-keywords): Use defconst. (matlab-font-lock-keywords): Rename to: (matlab-basic-font-lock-keywords): New and remove lines to: (matlab-file-basic-font-lock-keywords): New. (matlab-gaudy-font-lock-keywords): Rename to: (matlab-file-gaudy-font-lock-keywords): New and only use keywords for -file- and both. (matlab-mode): Use new named keywords. matlab-shell.el: (matlab*-font-lock-keywords): Use defconst. Use only keywords from matlab.el meant for both files and shell. (matlab-shell-object-output-font-lock-keywords): New --- matlab-shell.el | 42 +++++++++++++++----- matlab.el | 102 +++++++++++++++++++++++++++++------------------- 2 files changed, 94 insertions(+), 50 deletions(-) diff --git a/matlab-shell.el b/matlab-shell.el index b2943d8..78928f8 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -204,10 +204,9 @@ If multiple prompts are seen together, only call this once.") ;;; Font Lock ;; ;; Extra font lock keywords for the MATLAB shell. -(defvar matlab-shell-font-lock-keywords +(defconst matlab-shell-font-lock-keywords (list ;; Startup notices - ;; Various notices '(" M A T L A B " 0 'underline) '("All Rights Reserved" 0 'italic) '("\\(\\(?:(c)\\)?\\s-+Copyright[^\n]+\\)" 1 font-lock-comment-face) @@ -233,18 +232,41 @@ If multiple prompts are seen together, only call this once.") "Additional keywords used by MATLAB when reporting errors in interactive\ mode.") -(defvar matlab-shell-font-lock-keywords-1 - (append matlab-font-lock-keywords matlab-shell-font-lock-keywords) - "Keyword symbol used for font-lock mode.") +(defconst matlab-shell-font-lock-keywords-1 + (append matlab-basic-font-lock-keywords + matlab-shell-font-lock-keywords) + "Keyword symbol used for basic font-lock for MATLAB shell.") -(defvar matlab-shell-font-lock-keywords-2 - (append matlab-shell-font-lock-keywords-1 matlab-gaudy-font-lock-keywords) - "Keyword symbol used for gaudy font-lock symbols.") +(defconst matlab-shell-object-output-font-lock-keywords + (list + ;; disp of objects usually looks like this: + '("^\\s-*\\(\\w+\\) with properties:" (1 font-lock-type-face)) + ;; object output - highlight property names after 'with properties:' indicator + ;; NOTE: Normally a block like this would require us to use `font-lock-multiline' feature + ;; but since this is shell output, and not a thing you edit, we can skip it and rely + ;; on matlab-shell dumping the text as a unit. + '("^\\s-*\\(\\w+ with properties:\\)\n\\s-*\n" + ("^\\s-*\\(\\w+\\):[^\n]+$" ;; match the property before the : + ;; Extend search region across lines. + (save-excursion (re-search-forward "\n\\s-*\n" nil t) + (beginning-of-line) + (point)) + nil + (1 font-lock-variable-name-face))) + '("[[{]\\([0-9]+\\(?:x[0-9]+\\)+ \\w+\\)[]}]" (1 font-lock-comment-face)) + ) + "Highlight various extra outputs that are typical for MATLAB.") -(defvar matlab-shell-font-lock-keywords-3 +(defconst matlab-shell-font-lock-keywords-2 + (append matlab-shell-font-lock-keywords-1 + matlab-function-font-lock-keywords + matlab-shell-object-output-font-lock-keywords) + "Keyword symbol used for gaudy font-lock for MATLAB shell.") + +(defconst matlab-shell-font-lock-keywords-3 (append matlab-shell-font-lock-keywords-2 matlab-really-gaudy-font-lock-keywords) - "Keyword symbol used for really gaudy font-lock symbols.") + "Keyword symbol used for really gaudy font-lock for MATLAB shell.") ;;; ROOT ;; diff --git a/matlab.el b/matlab.el index 41efa62..5d6a7f0 100644 --- a/matlab.el +++ b/matlab.el @@ -1126,8 +1126,17 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (mapconcat (lambda (s) s) keywordlist "\\|")) "\\)\\>")) -;; font-lock keywords -(defvar matlab-font-lock-keywords +;;; Font Lock keyword handling +;; +;; Many parts of the keyword handling are shared with matlab-shell. +;; The matlab based variables here are divided up between generic keywords +;; and keywords only for M files. This means the M shell won't highlight +;; some syntaxes like classdef stuff even though someone might paste them in. +;; +;; matlab-*-keywords -- MATLAB Files or Shell +;; matlab-file-*-keywords -- MATLAB Files only + +(defconst matlab-basic-font-lock-keywords (list ;; charvec and string quote chars are also used as transpose, but only if directly ;; after characters, numbers, underscores, or closing delimiters. @@ -1141,37 +1150,10 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." ;; General keywords (list (matlab-font-lock-regexp-opt matlab-keyword-list) '(0 font-lock-keyword-face)) - ;; Keywords that should be the first word on a line - (list (concat "^\\s-*\\(" - (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) - "\\)") - '(1 font-lock-keyword-face)) ;; The end keyword is only a keyword when not used as an array ;; dereferencing part. '("\\(^\\|[;,]\\)[ \t]*\\(end\\)\\b" 2 (if (matlab-valid-end-construct-p) font-lock-keyword-face nil)) - ;; How about unreachable code? MUST BE AFTER KEYWORDS in order to - ;; get double-highlighting. - '(matlab-find-unreachable-code - (1 'underline prepend) ;if part - (2 'underline prepend) ;end part - (3 'underline prepend) ;else part (if applicable) - (4 font-lock-comment-face prepend) ;commented out part. - ) - ;; block comments need to be commented out too! - '(matlab-find-block-comments - (1 font-lock-comment-face prepend) ; commented out - (2 'underline prepend) - (3 'underline prepend) ;the comment parts - ) - ;; Cell mode breaks get special treatment - '("^\\s-*\\(%%[^\n]*\n\\)" (1 matlab-cellbreak-face append)) - ;; Highlight cross function variables - '(matlab-font-lock-cross-function-variables-match - (1 matlab-cross-function-variable-face prepend)) - ;; Highlight nested function/end keywords - '(matlab-font-lock-nested-function-keyword-match - (0 matlab-nested-function-keyword-face prepend)) ;; The global keyword defines some variables. Mark them. '("^\\s-*global\\s-+" ("\\(\\w+\\)\\(\\s-*=[^,; \t\n]+\\|[, \t;]+\\|$\\)" @@ -1187,13 +1169,46 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." ;; Imaginary number support '("\\<[0-9]\\.?\\(i\\|j\\)\\>" 1 font-lock-reference-face) ) - "Expressions to highlight in MATLAB mode.") + "Basic Expressions to highlight in MATLAB mode or shell.") +(defconst matlab-file-basic-font-lock-keywords + (append + matlab-basic-font-lock-keywords + (list + ;; Keywords that should be the first word on a line + (list (concat "^\\s-*\\(" + (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) + "\\)") + '(1 font-lock-keyword-face)) + ;; How about unreachable code? MUST BE AFTER KEYWORDS in order to + ;; get double-highlighting. + '(matlab-find-unreachable-code + (1 'underline prepend) ;if part + (2 'underline prepend) ;end part + (3 'underline prepend) ;else part (if applicable) + (4 font-lock-comment-face prepend) ;commented out part. + ) + ;; block comments need to be commented out too! + '(matlab-find-block-comments + (1 font-lock-comment-face prepend) ; commented out + (2 'underline prepend) + (3 'underline prepend) ;the comment parts + ) + ;; Cell mode breaks get special treatment + '("^\\s-*\\(%%[^\n]*\n\\)" (1 matlab-cellbreak-face append)) + ;; Highlight cross function variables + '(matlab-font-lock-cross-function-variables-match + (1 matlab-cross-function-variable-face prepend)) + ;; Highlight nested function/end keywords + '(matlab-font-lock-nested-function-keyword-match + (0 matlab-nested-function-keyword-face prepend)) + )) + "Basic Expressions to highlight in MATLAB Files.") (defconst matlab-function-arguments "\\(([^)]*)\\)?\\s-*\\([,;\n%]\\|$\\)") -(defvar matlab-function-font-lock-keywords +(defconst matlab-function-font-lock-keywords (list ;; defining a function, a (possibly empty) list of assigned variables, ;; function name, and an optional (possibly empty) list of input variables @@ -1239,7 +1254,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." "\\s-*\\(?2:(\\([^)]+\\))\\|\\)" "Regular expression for matching an attributes block.") -(defvar matlab-class-font-lock-keywords +(defconst matlab-file-class-font-lock-keywords (list ;; Classdefs keyword and the class name (list (concat "^\\s-*\\(classdef\\)" @@ -1273,17 +1288,17 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." ) "List of font-lock keywords used when an MATLAB file contains a class.") -(defvar matlab-gaudy-font-lock-keywords +(defconst matlab-file-gaudy-font-lock-keywords (append - matlab-font-lock-keywords + matlab-basic-font-lock-keywords + matlab-file-basic-font-lock-keywords matlab-function-font-lock-keywords - matlab-class-font-lock-keywords + matlab-file-class-font-lock-keywords ) "Expressions to highlight in MATLAB mode.") -(defvar matlab-really-gaudy-font-lock-keywords +(defconst matlab-really-gaudy-font-lock-keywords (append - matlab-gaudy-font-lock-keywords (list ;; Since it's a math language, how bout dem symbols? '("\\([<>~]=?\\|\\.[/*^']\\|==\\|\\\\|[-!^&|*+\\/~:]\\)" @@ -1316,6 +1331,13 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." )) "Expressions to highlight in MATLAB mode.") +(defconst matlab-file-really-gaudy-font-lock-keywords + (append + matlab-file-gaudy-font-lock-keywords + matlab-really-gaudy-font-lock-keywords + ) + "Expressions to highlight in MATLAB mode.") + ;; Imenu support. (defvar matlab-imenu-generic-expression '((nil "^\\s-*function\\>[ \t\n.]*\\(\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*\ @@ -1497,9 +1519,9 @@ All Key Bindings: ;; give each file it's own parameter history (make-local-variable 'matlab-shell-save-and-go-history) (make-local-variable 'font-lock-defaults) - (setq font-lock-defaults '((matlab-font-lock-keywords - matlab-gaudy-font-lock-keywords - matlab-really-gaudy-font-lock-keywords + (setq font-lock-defaults '((matlab-file-font-lock-keywords + matlab-file-gaudy-font-lock-keywords + matlab-file-really-gaudy-font-lock-keywords ) t ; do not do string/comment highlighting nil ; keywords are case sensitive. From e437a87761bf0ed6d38c4ba4ad6805d2f9123133 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Feb 2021 13:59:22 -0500 Subject: [PATCH 006/168] matlab-shell.el: (matlab-shell-save-and-go): Use 'emacsrun' insteaad of 'run'. toolbox/emacsrun.m: New file. Calls clear on the m file before running it. Used in matlab-shell-save-and-go. --- matlab-shell.el | 2 +- toolbox/emacsrun.m | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 toolbox/emacsrun.m diff --git a/matlab-shell.el b/matlab-shell.el index 78928f8..f00566b 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -2186,7 +2186,7 @@ Similar to `comint-send-input'." )) ;; If not changing dir, maybe we need to use 'run' command instead? - (let ((cmd (concat "run('" dir fn-name "')"))) + (let ((cmd (concat "emacsrun('" dir fn-name "')"))) (matlab-shell-send-command cmd))) )))) diff --git a/toolbox/emacsrun.m b/toolbox/emacsrun.m new file mode 100644 index 0000000..d2cdd57 --- /dev/null +++ b/toolbox/emacsrun.m @@ -0,0 +1,25 @@ +function emacsrun(mfile) +% Run code from MFILE. +% Assumes MFILE was recently edited, and proactively clears that function. +% +% Command sent by Emacs for save-and-go functionality + + if ~exist(mfile,'file') + error('You must save your file into a location accessible by MATLAB process.'); + end + + % Now figure out if shortFileName is on the path. + [ fullFilePath, shortFileName ] = fileparts(mfile); + onpath = ~isempty(which(shortFileName)); + + % If not on the path, temporarilly switch to that directory so it and an files it references are + % accessible + if ~onpath + oldpath = pwd; + cd(fullFilePath); + cleanup = onCleanup(@()cd(oldpath)); + end + + clear(shortFileName); + evalin('base',shortFileName); +end \ No newline at end of file From a746ccc931cb5a9e5775608576c19f3147b670ca Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Feb 2021 16:52:31 -0500 Subject: [PATCH 007/168] matlab.el: (matlab-file-basic-font-lock-keywords) (matlab-file-lass-font-lock-keywords): first-on-line keywords must be followed by specific charts to be highlighted. (matlab-find-prev-lie): Add optional input to ignore all comments. (matlab-prev-line): fix doc to note it will stop on comments. (matlab-cursor-on-vlaid-block-start): When finding context for 'arguments', use `matlab-find-prev-line' instead since arguments must be first actual code in the function. (matlab-beginning-of-command): Wrap impl in save-match-data. tests/Makefile: Only run "shelltests" if not on windows. tests/metest.el: (met-sexp-test-files, metest-indents-files): Add blocks.m tests/blocks.m: Add more cases abusing keywords as variables. Instrument for testing with metest.el. --- matlab.el | 110 +++++++++++++++++++-------------- tests/Makefile | 5 +- tests/blocks.m | 161 ++++++++++++++++++++++++++++++++---------------- tests/metest.el | 6 +- 4 files changed, 177 insertions(+), 105 deletions(-) diff --git a/matlab.el b/matlab.el index 5d6a7f0..41f308b 100644 --- a/matlab.el +++ b/matlab.el @@ -1175,10 +1175,11 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (append matlab-basic-font-lock-keywords (list - ;; Keywords that should be the first word on a line + ;; Keywords that should be the first word on a line AND + ;; also be alone, or have parameters after it. (list (concat "^\\s-*\\(" (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) - "\\)") + "\\)\\s-*[(,;%\n]") '(1 font-lock-keyword-face)) ;; How about unreachable code? MUST BE AFTER KEYWORDS in order to ;; get double-highlighting. @@ -1278,7 +1279,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." '("^\\s-*\\w+\\s-*\\(([:0-9,]+)\\s-*[^{=\n]+\\)" (1 font-lock-type-face nil nil)) ;; Properties blocks are full of variables - '("^\\s-*\\(properties\\|events\\|arguments\\)\\>" + '("^\\s-*\\(properties\\|events\\|arguments\\)\\s-*[(,;%\n]" ("^\\s-*\\(\\sw+\\)\\>" ;; This part matches the variable ;; extend region to match in (save-excursion (matlab-forward-sexp nil t) (beginning-of-line) (point)) @@ -1630,15 +1631,16 @@ All Key Bindings: (interactive) (message "matlab-mode, version %s" matlab-mode-version)) -(defun matlab-find-prev-line () +(defun matlab-find-prev-line (&optional ignorecomments) "Recurse backwards until a code line is found." (if (= -1 (forward-line -1)) nil (if (or (matlab-ltype-empty) - (matlab-ltype-comm-ignore)) - (matlab-find-prev-line) t))) + (matlab-ltype-comm-ignore) + (and ignorecomments (matlab-ltype-comm))) + (matlab-find-prev-line ignorecomments) t))) (defun matlab-prev-line () - "Go to the previous line of code. Return nil if not found." + "Go to the previous line of code or comment. Return nil if not found." (interactive) (let ((old-point (point))) (if (matlab-find-prev-line) t (goto-char old-point) nil))) @@ -2223,7 +2225,7 @@ Use this if you know what context you're in." ;; thus automatically true. t) - ;; Else, a special block. We need to check the context of this + ;; Else, a special block keyword. We need to check the context of this ;; block to know if this innerblock is valid. ;; Cheap check - if functions don't have end, then always invalid @@ -2231,31 +2233,44 @@ Use this if you know what context you're in." ((not matlab-functions-have-end) t) - ;; Cheap check - is this block keyword not the first word on the line? + ;; Cheap check - is this block keyword not the first word for this command? ((save-excursion (skip-syntax-backward " ") (not (bolp))) ;; Not first on line, not valid block. ;; Technically it COULD be valid, but we need some cheap ways ;; to skip over some types of syntaxes that look dumb. nil) + + ;; Cheap check - is this at the beginning of a command line (ignore ... ) + ((not (eq (point) (save-excursion (matlab-beginning-of-command) (point)))) + ;; If this statement is not also the first word on this command + ;; then it can't be one of these features. + nil) ;; Expensive check - is this block in the right context? ((or matlab-valid-block-start-slow-and-careful known-parent-block) - (let ((foundblock (match-string-no-properties 1)) - (myblock (if known-parent-block - (cons known-parent-block nil) ;; shortcut if known - (matlab-current-syntactic-block)))) - (cond ((and (string= foundblock "arguments") - (string= (car myblock) "function")) - ;; We found correct usage of arguments. - t) - ((string= (car myblock) "classdef") - ;; We found correct usage of methods, events, etc. - t) - (t - ;; Some other case is bad. - nil))) - ) + (let ((foundblock (match-string-no-properties 1))) + (cond + ((string= foundblock "arguments") + ;; Argument is only valid if it is the FIRST thing in a funtion. + (save-excursion + (if (and (matlab-find-prev-line t) (looking-at "\\s-*function\\>")) + ;; If the previous code line (ignoring whitespace and comments) + ;; is 'arguments', then that is a valid block. + t + ;; Otherewise, all other argument cases are bad. + nil))) + ;; Other special blocks are in a class. Go look. + (t + (let ((myblock (if known-parent-block + (cons known-parent-block nil) ;; shortcut if known + (matlab-current-syntactic-block)))) + (if (string= (car myblock) "classdef") + ;; We found correct usage of methods, events, etc. + t + ;; Some other case is bad. + nil))) + ))) ;; A cheap version of the expensive check ((and (not matlab-valid-block-start-slow-and-careful) @@ -2348,30 +2363,31 @@ If `matlab-functions-have-end', skip over functions with end." Travels across continuations." (interactive) (beginning-of-line) - (let ((p nil) - ;; This restriction is a wild guess where to end reverse - ;; searching for array continuations. The reason is that - ;; matlab up list is very slow, and most people would never - ;; put a blank line in a matrix. Either way, it's worth the - ;; trade off to speed this up for large files. - ;; This list of keywords is NOT meant to be comprehensive. - (r (save-excursion - (re-search-backward - "^\\s-*\\(%\\|if\\|else\\(if\\)\\|while\\|\\(par\\)?for\\|$\\)\\>" - nil t))) - (bc (matlab-ltype-block-comm))) - (if bc - ;; block comment - just go to the beginning. - (goto-char (car bc)) + (save-match-data + (let ((p nil) + ;; This restriction is a wild guess where to end reverse + ;; searching for array continuations. The reason is that + ;; matlab up list is very slow, and most people would never + ;; put a blank line in a matrix. Either way, it's worth the + ;; trade off to speed this up for large files. + ;; This list of keywords is NOT meant to be comprehensive. + (r (save-excursion + (re-search-backward + "^\\s-*\\(%\\|if\\|else\\(if\\)\\|while\\|\\(par\\)?for\\|$\\)\\>" + nil t))) + (bc (matlab-ltype-block-comm))) + (if bc + ;; block comment - just go to the beginning. + (goto-char (car bc)) - ;; Scan across lines that are related. - (while (and (or (matlab-prev-line-cont) - (matlab-ltype-continued-comm) - (setq p (matlab-lattr-array-cont r))) - (save-excursion (beginning-of-line) (not (bobp)))) - (if p (goto-char p) (matlab-prev-line)) - (setq p nil))) - (back-to-indentation))) + ;; Scan across lines that are related. + (while (and (or (matlab-prev-line-cont) + (matlab-ltype-continued-comm) + (setq p (matlab-lattr-array-cont r))) + (save-excursion (beginning-of-line) (not (bobp)))) + (if p (goto-char p) (matlab-prev-line)) + (setq p nil))) + (back-to-indentation)))) (defun matlab-end-of-command (&optional beginning) "Go to the end of an M command. diff --git a/tests/Makefile b/tests/Makefile index 11276a7..7f8ccd4 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -20,8 +20,11 @@ VERSION=4.0 DISTDIR=$(top)matlab-emacs-$(VERSION)/tests - +ifdef OS # No MATLAB shell tests on windows. +all: tests matlab modetests +else # The OS variable is only defined on windows, so linux systems can run shell tests. all: tests matlab modetests shelltests +endif .PHONY: modetests modetests: metest.elc diff --git a/tests/blocks.m b/tests/blocks.m index 4bf150d..bc954b1 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -1,80 +1,133 @@ +% >>1 classdef blocks < handle % !!0 - properties - normalprop = 1; - end +% >>11 + properties %!!4 + normalprop = 1; %!!8 + end % <<11 - properties(Access='public') + %>>12 + properties(Access='public') %!!4 % See if we can create properties using keywords %properties = 1; %methods = 1; %events = 1; - arguments + arguments %!!8 prop = 1; - end + end %<<12 - events (Access='private') + % >> 13 + events (Access='private') %!!4 %properties %events %methods - arguments - misc - end + arguments %!!8 + misc %!!8 + end % <<13 - methods + %>>14 + methods %!!4 - function simple_method(obj) - arguments - obj - end + %>>15 + function simple_method(obj) %!!8 + %>>151 + arguments %!!12 + obj %!!16 + end %<<151 disp(obj.normalprop); - end + end %<<15 - function obj = blocks(arguments,events,properties,methods,enumeration,normal) + %>>16 + function obj = blocks(arguments,events,properties,methods,enumeration,normal)%!!8 + %>>161 + arguments %!!12 + arguments%!!16 + events%!!16 + properties%!!16 + methods%!!16 + enumeration%!!16 + normal%!!16 + end %<<161 - arguments - arguments - events - properties - methods - enumeration - normal - end - - obj.prop = arguments; - obj.prop = events; - obj.prop = properties; - obj.prop = methods; - obj.prop = enumeration; - obj.prop = normal; - end + obj.prop = arguments;%!!12 + obj.prop = events;%!!12 + obj.prop = properties;%!!12 + obj.prop = methods;%!!12 + obj.prop = enumeration;%!!12 + obj.prop = normal;%!!12 + end %<<16 - function properties(~) - end + %>>17 + function properties(~)%!!8 + end %<<17 - function methods(~) - end + %>>18 + function methods(~)%!!8 + end %<<18 - function events(~) - end + %>>19 + function events(~)%!!8 + end %<<19 - function arguments(~) - end + %>>20 + function arguments(~)%!!8 + end %<<20 - function enumeration(~) - end - - function usestuff(obj) + %>>21 + function enumeration(~)%!!8 + end %<<21 + + %>>22 + function usestuff(obj)%!!8 % Try using the methods of this object - obj.properties(); - obj.methods(); - obj.events(); - obj.arguments(); - obj.enumeration(); - end + obj.properties();%!!12 + obj.methods();%!!12 + obj.events();%!!12 + obj.arguments();%!!12 + obj.enumeration();%!!12 + normal();%!!12 + end %<<22 + + %>>23 + function [m,s] = blarg(~, arguments)%!!8 + % Starter comments. + + %>>231 + arguments%!!12 + ~%!!16 + arguments(:,:) {mustBeNumeric}%!!16 + end %<<231 + + m = mean( ...%!!12 + arguments, 'all'); %!!16 + + arguments = 1;%!!12 + events = arguments;%!!12 + methods = events;%!!12 + properties = methods;%!!12 + enumeration = properties;%!!12 + + s = enumeration;%!!12 + end %<<23 + + %>>24 + function s = simple(~,arguments)%!!8 + % Simple function + + events = arguments;%!!12 + methods = events;%!!12 + properties = methods;%!!12 + enumeration = properties;%!!12 + + s = enumeration; %!!12 + end %<<24 + + %!!8 + end %<<14 - end - -end \ No newline at end of file + %!!4 +end % <<1 + +%!!0 diff --git a/tests/metest.el b/tests/metest.el index 3e4920b..213f667 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -93,11 +93,11 @@ )) (message "")) -(defvar met-sexptest-files '("expressions.m" "mclass.m") +(defvar met-sexptest-files '("expressions.m" "mclass.m" "blocks.m") "List of files for running syntactic expression tests.") (defun metest-sexp-counting-test () - "Run a test to make sure string nd comment highlighting work." + "Run a test to make sure string and comment highlighting work." (dolist (F met-sexptest-files) (let ((buf (find-file-noselect (expand-file-name F met-testfile-path))) (cnt 0)) @@ -172,7 +172,7 @@ (message "")) -(defvar met-indents-files '("indents.m" "mclass.m") +(defvar met-indents-files '("indents.m" "mclass.m" "blocks.m") "List of files for running syntactic indentation tests.") (defun metest-indents-test () From e2f684ddc3d57002b3ac69dafc00b6d51a2bb759 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 28 Feb 2021 06:34:33 -0500 Subject: [PATCH 008/168] tests/blocks.m Add some more torture tests around methods, events, and argument blocks from John. --- tests/blocks.m | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/blocks.m b/tests/blocks.m index bc954b1..4f80c51 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -9,21 +9,25 @@ classdef blocks < handle %>>12 properties(Access='public') %!!4 + % See if we can create properties using keywords %properties = 1; %methods = 1; %events = 1; arguments %!!8 prop = 1; + end %<<12 % >> 13 events (Access='private') %!!4 + %properties %events %methods arguments %!!8 misc %!!8 + end % <<13 %>>14 @@ -72,9 +76,20 @@ classdef blocks < handle end %<<19 %>>20 - function arguments(~)%!!8 + function events=arguments(arguments)%!!8 + arguments, arguments(:,:) {mustBeNumeric}, end %!!12 + + enumeration ... %!!12 + = arguments; + + if enumeration > 0 %!!12 + arguments = -enumeration; %!!16 + end %!!12 + + events ... %!!12 + = arguments + 1; end %<<20 - + %>>21 function enumeration(~)%!!8 end %<<21 @@ -123,7 +138,11 @@ classdef blocks < handle s = enumeration; %!!12 end %<<24 - + + function methods=foo3(obj,properties) %!!8 + methods=obj.arguments(properties); %!!12 + end %!!8 + %!!8 end %<<14 From ede79ff02d00ef482873aa95dcd53e1274355c27 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 28 Feb 2021 11:25:58 -0500 Subject: [PATCH 009/168] matlab.el: Revamp how function...end is handled. (matlab-functions-have-end): Set default to 'guess. (matlab-functions-have-end-minor-mode): Set the lighter to be dynamic. Set matlab-functions-have-end to the script type, or guess. (matlab-guess-script-type): New (matlab-do-functions-have-end-p): Set directly based on script type. Only navigate to detect end for functions. (matlab-toggle-functions-have-end-minor-mode): No not set `matlab-functions-have-end'. This is done in the minor mode call. (matlab-find-code-line): New utility used by `matlab-guess-script-type'. (matlab-defun-regex): Move whitespace outside of first group. (matlab-indent-line): Prevent move-to-column from ever getting a negative number. (matlab-calculate-indentation-1): Replace setting of functions have end to instead call the minor mode which will do the work. (matlab-mode-vf-add-ends): Only skip check in fast mode. (matlab-mode-vf-block-matches-forward): Re-detect with `matlab-guess-script-type'. Check script type against existing value of matlab-functions-have-end and auto update quietly with no questions. Use script type to minimize when questions are asked when we do check the blocks. Always use minor-mode to set value of matlab-functions-have-end. --- matlab.el | 189 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 154 insertions(+), 35 deletions(-) diff --git a/matlab.el b/matlab.el index 41f308b..b9a39a6 100644 --- a/matlab.el +++ b/matlab.el @@ -142,7 +142,7 @@ changed, and functions are indented based on `matlab-functions-have-end'." (make-variable-buffer-local 'matlab-indent-function-body) -(defcustom matlab-functions-have-end t +(defcustom matlab-functions-have-end 'guess "*If non-nil, functions-have-end minor mode is on by default. If the value is 'guess, then we guess if a file has end when `matlab-mode' is initialized." @@ -160,32 +160,74 @@ If the value is 'guess, then we guess if a file has end when (easy-mmode-define-minor-mode matlab-functions-have-end-minor-mode "Toggle functions-have-end minor mode, indicating function/end pairing." nil - " function...end" + (:eval (cond ((eq matlab-functions-have-end 'guess) + " function...?") + ((eq matlab-functions-have-end 'class) + " classdef...end") + (matlab-functions-have-end + " function...end") + (t + " function..."))) nil ; empty mode-map ;; body of matlab-functions-have-end-minor-mode - (if matlab-functions-have-end-minor-mode - (setq matlab-functions-have-end t) - (setq matlab-functions-have-end nil) - ) + (let ((type (matlab-guess-script-type))) + (if matlab-functions-have-end-minor-mode + (if (eq type 'empty) + (setq matlab-functions-have-end 'guess) + (setq matlab-functions-have-end type)) + (setq matlab-functions-have-end nil) + )) ) +(defun matlab-guess-script-type () + "Guess the type of script this `matlab-mode' file contains. +Returns one of 'empty, 'script, 'function, 'class." + (save-excursion + (goto-char (point-min)) + (if (matlab-find-code-line) + ;; We found some code, what is it? + (if (looking-at matlab-defun-regex) + ;; A match - figure out the type of thing. + (let ((str (match-string-no-properties 1))) + (cond ((string= str "function") + 'function) + ((string= str "classdef") + 'class))) + ;; No function or class - just a script. + 'script) + ;; No lines of code, we are empty, so undecided. + 'empty))) + (defvar matlab-defun-regex) ;; Quiet compiler warning (is defined below) -(defun matlab-do-functions-have-end-p () +(defun matlab-do-functions-have-end-p (&optional no-navigate) "Look at the contents of the current buffer and decide if functions have end. If the current value of `matlab-functions-have-end' is 'guess, look @ the buffer. If the value is t, then return that." (if (eq matlab-functions-have-end 'guess) - (save-excursion - (goto-char (point-min)) - (if (re-search-forward matlab-defun-regex nil t) - (let ((matlab-functions-have-end t)) - (beginning-of-line) - (condition-case nil - (progn (matlab-forward-sexp) t) - (error nil)) - ) - nil - ) + ;; Lets guess what we think the answer is. + (let ((type (matlab-guess-script-type))) + (cond ((eq type 'empty) + 'guess) ;; Keep guessing until we get some code. + ((eq type 'script) + 'script) ;; modern scripts can have functions, and they are required to have an end. + ((eq type 'class) + 'class) ;; classes always have ends. + (no-navigate + ;; Functions, but don't navigate ... stay in guess mode. + 'guess) + (t + ;; functions but do navigate - we need to see if there is an end. + (save-excursion + (goto-char (point-min)) + (matlab-find-code-line) + (let ((matlab-functions-have-end t)) ;; pretend we have ends + (beginning-of-line) + (condition-case nil + ;; Try to navigate. If success, then t + (progn (matlab-forward-sexp) t) + ;; On failure, then no ends. + (error nil)) + )))) ) ;; Else, just return the default. matlab-functions-have-end)) @@ -197,7 +239,7 @@ If the value is t, then return that." (progn (matlab-functions-have-end-minor-mode -1) (error "Mode `matlab-functions-have-end' minor mode is only for MATLAB Major mode"))) - (setq matlab-functions-have-end matlab-functions-have-end-minor-mode)) + ) (defun matlab-indent-function-body-p () "Non-nil if functions bodies are indented. @@ -1551,9 +1593,9 @@ All Key Bindings: ;; If first function is terminated with an end statement, then functions have ;; ends. (if (matlab-do-functions-have-end-p) + ;; minor mode now treat's 'guess' as true when passing in 1. (matlab-functions-have-end-minor-mode 1) - (matlab-functions-have-end-minor-mode -1) - ) + (matlab-functions-have-end-minor-mode -1)) ;; When matlab-indent-function-body is set to 'MathWorks-Standard, ;; - we indent all functions that terminate with an end statement @@ -1645,6 +1687,17 @@ All Key Bindings: (let ((old-point (point))) (if (matlab-find-prev-line) t (goto-char old-point) nil))) +(defun matlab-find-code-line () + "Walk forwards until we are on a line of code return t on success. +If the currnet line is code, return immediately. +Ignore comments and whitespace." + (if (or (matlab-ltype-empty) + (matlab-ltype-comm)) + (if (= 1 (forward-line 1)) + nil ;; end of buffer. + (matlab-find-code-line)) ;; try again. + t)) + (defun matlab-uniquify-list (lst) "Return a list that is a subset of LST where all elements are unique." (let ((nlst nil)) @@ -1770,7 +1823,7 @@ Return nil if it is being used to dereference an array." ;; "-pre" means "partial regular expression" ;; "-if" and "-no-if" means "[no] Indent Function" -(defconst matlab-defun-regex "^\\(\\s-*function\\|classdef\\)[ \t.[]" +(defconst matlab-defun-regex "^\\s-*\\(function\\|classdef\\)[ \t.[]" "Regular expression defining the beginning of a MATLAB function.") (defconst matlab-mcos-innerblock-regexp "properties\\|methods\\|events\\|enumeration\\|arguments" @@ -2970,7 +3023,7 @@ If there isn't one, then return nil, point otherwise." (beginning-of-line) (delete-horizontal-space) (indent-to i))) - (if (<= cc ci) (move-to-column i)) + (if (<= cc ci) (move-to-column (max 0 i))) )) (defun matlab-calc-indent () @@ -3073,8 +3126,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (if end-of-function (if (or matlab-functions-have-end (if (yes-or-no-p matlab-functions-have-end-should-be-true) - ;; TODO - ask user to reindent the fcn now? - (setq matlab-functions-have-end t) + (matlab-functions-have-end-minor-mode 1) (error "Unmatched end"))) (if (matlab-indent-function-body-p) (setq ci (- ci matlab-indent-level)))) @@ -4407,7 +4459,9 @@ Optional argument FAST is ignored." (defun matlab-mode-vf-add-ends (&optional fast) "Verify/Fix adding ENDS to functions. Optional argument FAST skips this test in fast mode." - (when (and matlab-functions-have-end (not fast)) + ;; We used to do extra checking here, but now we do + ;; checking in the verifier + (when (not fast) (matlab-mode-vf-block-matches-forward nil t) )) @@ -4420,11 +4474,55 @@ not be needed. Optional argument FAST causes this check to be skipped. Optional argument ADDEND asks to add ends to functions, and is used by `matlab-mode-vf-add-ends'" - (goto-char (point-min)) (let ((go t) - (expr (concat "\\<\\(" (matlab-block-beg-pre) "\\)\\>")) + (expr nil) + ;; lets avoid asking questions based on id of this file + ;; and if ends are optional in the first place. + (filetype (matlab-guess-script-type)) ) + ;; Before checking syntax, lets re-look at the file if we were in + ;; guess mode and re-assert what we should do. + (cond + ;; If the file is empty of code (from before, or just now) + ;; then optimize out this step. + ((eq filetype 'empty) + ;; No code, no need to loop. + (setq fast t) + ;; If user deleted content, go back into guess mode. + (setq matlab-functions-have-end 'guess) + (matlab-functions-have-end-minor-mode 1) + ) + + ;; If we are in guess mode, but user added content, we can + ;; not have a fresh new guess. + ((eq matlab-functions-have-end 'guess) + (let ((guess (matlab-do-functions-have-end-p 'no-navigate))) + (if guess (matlab-functions-have-end-minor-mode 1) + (matlab-functions-have-end-minor-mode -1))) + ) + + ;; If we are in no-end mode, BUT the filetype is wrong, say something. + ((and (not matlab-functions-have-end) (or (eq filetype 'script) (eq filetype 'class))) + (message "Type of file detected no longer matches `matlab-functions-have-end' of nil, assume t.") + (matlab-functions-have-end-minor-mode 1) + (sit-for 1) + ) + + ;; If functions have end but the style changes, re-up the lighter on the minor mode. + ;; note, we can ignore that 'empty == 'guess b/c handled earlier. + ((and matlab-functions-have-end (not (eq matlab-functions-have-end filetype))) + (matlab-functions-have-end-minor-mode 1)) + + ;; If the variable was specified and file is not empty, then do nothing. + ;; TODO - maybe we should force to t for scripts and classes? + ) + + ;; compute expression after changing state of funtions have end above. + (setq expr (concat "\\<\\(" (matlab-block-beg-pre) "\\)\\>")) + + ;; Navigate our sexp's and make sure we're all good. (matlab-navigation-syntax + (goto-char (point-min)) (while (and (not fast) go (re-search-forward expr nil t)) (forward-word -1) ;back over the special word (let ((s (point)) @@ -4441,7 +4539,23 @@ by `matlab-mode-vf-add-ends'" (setq go nil))) (forward-word 1)) (error (setq go nil))) - (when (not go) + + (cond + ;; If we are still in guess mode and file is good, then we now have our answer. + ((and go (eq matlab-functions-have-end 'guess)) + (matlab-functions-have-end-minor-mode 1)) + + ;; If we had an error and still in guess mode we can look at the latest + ;; content and decide if we should have ends anyway. + ((and (not go) (eq matlab-functions-have-end 'guess)) + (if (matlab-do-functions-have-end-p 'no-navigate) + ;; minor mode now looks at file type when passing in 1 + (matlab-functions-have-end-minor-mode 1) + ;; Turn off for this file. No questions. + (matlab-functions-have-end-minor-mode -1))) + + ;; If we had an error, but none of the above, try to fix? + ((not go) (goto-char s) (setq e (save-excursion (forward-word 1) (point))) ;; Try to add an end to the broken block @@ -4451,18 +4565,23 @@ by `matlab-mode-vf-add-ends'" (progn (matlab-mode-vf-add-end-to-this-block) (setq go t)) - ;; Else, mark this buffer as not needing ends. - (setq matlab-functions-have-end nil) - (message "Marking buffer as not needing END for this session.") - (sit-for 1) - ) + ;; Else, mark this buffer as not needing ends, + ;; but ONLY if a function buffer + (when (eq filetype 'function) + (if (matlab-mode-highlight-ask + s e "Should funtions have end in this file?") + (matlab-functions-have-end-minor-mode 1) + (matlab-functions-have-end-minor-mode -1) + (message "Marking buffer as not needing END for this session.") + (sit-for 1)))) ;; We aren't in addend mode then we are in plain verify ;; mode (if (matlab-mode-highlight-ask s e "Unterminated block. Continue anyway?") nil ;; continue anyway. - (error "Unterminated Block found!"))))) + (error "Unterminated Block found!")))) + )) ;; cond, let (message "Block-check: %d%%" (/ (/ (* 100 (point)) (point-max)) 2)))))) (defun matlab-mode-vf-add-end-to-this-block () From 9872d929ec468e25cd43c7cac58024f793b74ffc Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 28 Feb 2021 12:21:27 -0500 Subject: [PATCH 010/168] matlab.el: Speed up checking for valid class block keywords. (matlab-find-prev-code-line): New. Looks for a previous line of code and navs to the beginning of it. (matlab-cursor-on-valid-block-start): Stop navigating for full syntactic block check, just look at prev command and verify it belongs to a class and assume it is correct. (matlab-previous-line-belongs-to-classdef-p): New. Verify previous thing is a class based keyword. (matlab-calculate-indentation-1): When indenting `end' ask matlab-backward-sexp to not throw errors. --- matlab.el | 71 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/matlab.el b/matlab.el index b9a39a6..df7b5c8 100644 --- a/matlab.el +++ b/matlab.el @@ -179,6 +179,7 @@ If the value is 'guess, then we guess if a file has end when )) ) +(defvar matlab-defun-regex) ;; Quiet compiler warning (is defined below) (defun matlab-guess-script-type () "Guess the type of script this `matlab-mode' file contains. Returns one of 'empty, 'script, 'function, 'class." @@ -198,7 +199,6 @@ Returns one of 'empty, 'script, 'function, 'class." ;; No lines of code, we are empty, so undecided. 'empty))) -(defvar matlab-defun-regex) ;; Quiet compiler warning (is defined below) (defun matlab-do-functions-have-end-p (&optional no-navigate) "Look at the contents of the current buffer and decide if functions have end. If the current value of `matlab-functions-have-end' is 'guess, look @ the buffer. @@ -1673,6 +1673,16 @@ All Key Bindings: (interactive) (message "matlab-mode, version %s" matlab-mode-version)) +(defun matlab-find-prev-code-line () + "Navigate backward until a code line is found. +Navigate across continuations until we are at the beginning of +that command. +Return t on success, nil if we couldn't navigate backwards." + (let ((ans (matlab-find-prev-line 'ignore-comments))) + (when ans + (matlab-beginning-of-command) + ans))) + (defun matlab-find-prev-line (&optional ignorecomments) "Recurse backwards until a code line is found." (if (= -1 (forward-line -1)) nil @@ -2307,22 +2317,34 @@ Use this if you know what context you're in." ((string= foundblock "arguments") ;; Argument is only valid if it is the FIRST thing in a funtion. (save-excursion - (if (and (matlab-find-prev-line t) (looking-at "\\s-*function\\>")) + (if (and (matlab-find-prev-code-line) (looking-at "\\s-*function\\>")) ;; If the previous code line (ignoring whitespace and comments) ;; is 'arguments', then that is a valid block. t ;; Otherewise, all other argument cases are bad. nil))) - ;; Other special blocks are in a class. Go look. + + ;; Other special blocks are in a class. If not in a class file, fail. + ((not (eq matlab-functions-have-end 'class)) + nil) + + ;; This is a speed test for classdef stuff. It only navigates backward + ;; one step. If the previous thing also belongs to a class, then we must + ;; be in a class. + ((matlab-previous-line-belongs-to-classdef-p) + t) + ;; We are in a class, so identify if this is in a class context. (t - (let ((myblock (if known-parent-block - (cons known-parent-block nil) ;; shortcut if known - (matlab-current-syntactic-block)))) - (if (string= (car myblock) "classdef") - ;; We found correct usage of methods, events, etc. - t - ;; Some other case is bad. - nil))) + ;; (let ((myblock (if known-parent-block + ;; (cons known-parent-block nil) ;; shortcut if known + ;; (matlab-current-syntactic-block)))) + ;; (if (string= (car myblock) "classdef") + ;; ;; We found correct usage of methods, events, etc. + ;; t + ;; ;; Some other case is bad. + nil) + ;;)) + ))) ;; A cheap version of the expensive check @@ -2336,6 +2358,31 @@ Use this if you know what context you're in." (t nil) )))) +(defun matlab-previous-line-belongs-to-classdef-p () + "Return the nature of the line of code before this one. +Ignores comments, etc. +Returns non-nil if that previous thing is unique to a classdef." + (save-excursion + (save-match-data + ;; Move to the syntax in question. + (if (not (matlab-find-prev-code-line)) + nil ;; No code + ;; Lets see what it is. + (back-to-indentation) + ;; Is it an end? Nav backward + (if (looking-at "\\") + (if (not (matlab-backward-sexp t t)) + nil ;; failed to nav - just skip it. + ;; If no errors so far, compute the kind of block. + (if (looking-at matlab-innerblock-syntax-re) + t ;; found something that belongs to a classdef. + nil)) + ;; Not on an end. Is that because we are now on a classdef? + ;; If so, then this is our context, so that's ok. + (if (looking-at "\\") + t ;; Yep, that's a class + nil)))))) + (defun matlab-current-syntactic-block () "Return information about the current syntactic block the cursor is in. Value returned is of the form ( BLOCK-TYPE . PT ) where BLOCK-TYPE is a @@ -3121,7 +3168,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (let ((matlab-functions-have-end t)) (save-excursion (beginning-of-line) - (matlab-backward-sexp t) ;; may throw "unstarted block" error + (matlab-backward-sexp t t) (matlab-ltype-function-definition))))) (if end-of-function (if (or matlab-functions-have-end From 44e19af23db508f042aab07f03aeea247c6a00ee Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 1 Mar 2021 19:09:38 -0500 Subject: [PATCH 011/168] matlab.el: (matlab-cursor-on-valid-block-start): Replaced previous fix by only looking at the previous end and verifying it is indented by the standard level. This (of course) assumes that the whole file is indented correctly. --- matlab.el | 60 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/matlab.el b/matlab.el index df7b5c8..d3bbbf1 100644 --- a/matlab.el +++ b/matlab.el @@ -2294,7 +2294,7 @@ Use this if you know what context you're in." ;; Cheap check - if functions don't have end, then always invalid ;; since these context blocks can't exist. ((not matlab-functions-have-end) - t) + nil) ;; Cheap check - is this block keyword not the first word for this command? ((save-excursion (skip-syntax-backward " ") (not (bolp))) @@ -2328,30 +2328,56 @@ Use this if you know what context you're in." ((not (eq matlab-functions-have-end 'class)) nil) - ;; This is a speed test for classdef stuff. It only navigates backward + ;; If a known parent block was passed in, then this will be a very + ;; fast check, so do that next. If it is the right thing, then + ;; true! + (known-parent-block + (if (string= known-parent-block "classdef") + t + ;; otherwise not correct. + nil)) + + ;; This test goes back one command. If it is an end, and it is indented just + ;; one level, then we are good b/c we already check we're in a classdef file. + ((save-excursion + (if (not (matlab-find-prev-code-line)) + nil ;; no code, so not in a class. + (back-to-indentation) + (or + ;; Looking at correctly indented end + (and (looking-at "\\") + (eq (current-indentation) matlab-indent-level)) + ;; Looking at the classdef itself + (looking-at "\\") ))) + t) + + ;; This is a medium speed test for classdef stuff. It only navigates backward ;; one step. If the previous thing also belongs to a class, then we must ;; be in a class. - ((matlab-previous-line-belongs-to-classdef-p) - t) - ;; We are in a class, so identify if this is in a class context. - (t - ;; (let ((myblock (if known-parent-block - ;; (cons known-parent-block nil) ;; shortcut if known - ;; (matlab-current-syntactic-block)))) - ;; (if (string= (car myblock) "classdef") - ;; ;; We found correct usage of methods, events, etc. - ;; t - ;; ;; Some other case is bad. - nil) - ;;)) + ;;((matlab-previous-line-belongs-to-classdef-p) t) + + ;; This is a slow operation - navigating backward to find the current syntactic + ;; block is pretty expensive, but it also always gets it right. + ;; + ;; Since We are in a class, identify if this is in a class context. + ;; ((let ((myblock (if known-parent-block + ;; (cons known-parent-block nil) ;; shortcut if known + ;; (matlab-current-syntactic-block)))) + ;; (string= (car myblock) "classdef")) + ;; ;; We found correct usage of methods, events, etc. + ;; t) + + ;; All else fails - so not valid. + (t nil) - ))) + ))) ;; End slow-and-careful ;; A cheap version of the expensive check ((and (not matlab-valid-block-start-slow-and-careful) (looking-at matlab-innerblock-syntax-re)) ;; If we are not slow and careful, we just need to return t if we see - ;; of of these keywords since these were filtered out earlier. + ;; one of these keywords since other cases where these weren't 1st on the line + ;; or not in a classdef file are already filtered out. t) ;; If none of the valid cases, must be invalid From 26875b95170a79a2ec870988d6d6ab178a655225 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 4 Mar 2021 15:28:46 -0500 Subject: [PATCH 012/168] matlab.el: (matlab-block-end-pre): Reverse choice of regex used if functions have end. This appears to have been backwards for a very long time. (tests/blocks.m, tests/indents/m, tests/mclass.m): Save with correct indentation for blank lines. Add tests with function ... end on the same line. --- matlab.el | 8 +++++--- tests/blocks.m | 2 +- tests/indents.m | 15 +++++++++++---- tests/mclass.m | 25 ++++++++++++++++++------- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/matlab.el b/matlab.el index d3bbbf1..d01a5fa 100644 --- a/matlab.el +++ b/matlab.el @@ -1903,8 +1903,8 @@ Excludes function.") (defun matlab-block-end-pre () "Partial regular expression to recognize MATLAB block-end keywords." (if matlab-functions-have-end - matlab-block-end-pre-if - matlab-block-end-pre-no-if)) + matlab-block-end-pre-no-if + matlab-block-end-pre-if)) ;; Not used. ;;(defconst matlab-other-pre @@ -3438,6 +3438,7 @@ See `matlab-calculate-indentation'." (setq bc (1- bc)) (if (>= ci matlab-indent-level) (setq bc -1)))) + ;; Else, funtions don't have ends in this file. (if (and (matlab-indent-function-body-p) (matlab-ltype-function-definition)) (setq bc (1+ bc)))) ;; Remove 1 from the close count if there is an END on the beginning @@ -4588,7 +4589,8 @@ by `matlab-mode-vf-add-ends'" ;; If the variable was specified and file is not empty, then do nothing. ;; TODO - maybe we should force to t for scripts and classes? - ) + + ) ;; end cond ;; compute expression after changing state of funtions have end above. (setq expr (concat "\\<\\(" (matlab-block-beg-pre) "\\)\\>")) diff --git a/tests/blocks.m b/tests/blocks.m index 4f80c51..43b57c6 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -78,7 +78,7 @@ classdef blocks < handle %>>20 function events=arguments(arguments)%!!8 arguments, arguments(:,:) {mustBeNumeric}, end %!!12 - + enumeration ... %!!12 = arguments; diff --git a/tests/indents.m b/tests/indents.m index 146156b..67b98ba 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -132,7 +132,7 @@ function C = block_starts_in_comments_and_strings() % see previous function % !!4 for x=1:length(C) % !!4 - + % !!8 end @@ -149,7 +149,7 @@ function C = block_starts_in_comments_and_strings() try % !!8 catch %!!4 - + % !!8 end @@ -217,6 +217,13 @@ function has_nested_fcn %!!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 +end%!!0 \ No newline at end of file diff --git a/tests/mclass.m b/tests/mclass.m index d16c286..a91ab85 100644 --- a/tests/mclass.m +++ b/tests/mclass.m @@ -51,13 +51,13 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# disp("while loop going on here ("); % #2# % !!52 - + % !!16 end % <<17 - + error('function mclass in charvec }'); % #2# - + % !!12 end % <<16 @@ -66,7 +66,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# methods (Access='public') % >>13 function meth(obj) % #3# - + % >>14 if obj.AP % #3# @@ -74,8 +74,8 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# else - % >>15 - try + % >>15 + try % comment with if, while, parfor words in it. @@ -91,13 +91,24 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# end % <<12 methods (Abstract, Hidden=true) % #2# - + result = abs_func(a,b) % #3# result = other_abs_fun(a,b) % #3# end + methods %!!4 + + function end_separate_line(~) %!!8 + end %!!8 + + function end_same_line(~), end %!!8 + + function after_end_same_line(~), end %!!8 + + end %!!4 + end % <<1 % End \ No newline at end of file From 02e9ea0a712983145d688ea2600ee253b7f15560 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 4 Mar 2021 20:13:31 -0500 Subject: [PATCH 013/168] mlint.el: Add autofix routines for missing ends, and missmatched file/class names. (mlint-error-id-fix-alist): Add ENDCT2, FNDEF, and MCFIL support. (mlint-lm-entry::mlint-fix-entry, mlint-lm-*::initialize-instance): Fix all occurances of :AFTER with :after. (mlint-lm-function-name): New class. (mlint-lm-function-name::initialize-instance): New. (mlint-lm-missing-end): New class. (mlint-lm-missing-end::mlint-fix-entry): New. --- mlint.el | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/mlint.el b/mlint.el index b1e4c6f..c28fe08 100644 --- a/mlint.el +++ b/mlint.el @@ -190,6 +190,9 @@ be cause for being turned off in a buffer." ( NOCOM . mlint-lm-delete-focus ) ( ST2NM . mlint-lm-str2num ) ( FDEPR . mlint-lm-entry-deprecated ) + ( ENDCT2 . mlint-lm-missing-end ) + ( FNDEF . mlint-lm-function-name ) + ( MCFIL . mlint-lm-function-name ) ) "List of warning IDs and auto-fix functions. If the CAR of an association matches an error id then the linemark entry @@ -468,7 +471,7 @@ ACTIVE-P if it should be made visible." "Return non-nil if entry E can be automatically fixed." (oref-default e fixable-p)) -(cl-defmethod mlint-fix-entry :AFTER ((e mlint-lm-entry)) +(cl-defmethod mlint-fix-entry :after ((e mlint-lm-entry)) "Stuff to do after a warning is considered fixed. Subclasses fulfill the duty of actually fixing the code." (linemark-display e nil) @@ -508,7 +511,7 @@ Subclasses fulfill the duty of actually fixing the code." "Class which can replace the focus area." :abstract t) -(cl-defmethod initialize-instance :AFTER ((this mlint-lm-replace-focus) +(cl-defmethod initialize-instance :after ((this mlint-lm-replace-focus) &rest fields) "Calculate the new fix description for THIS. Optional argument FIELDS are the initialization arguments." @@ -533,7 +536,7 @@ Optional argument FIELDS are the initialization arguments." "Entry for anything that is deprecated. Extracts the replacement for the deprecated symbol from the warning message.") -(cl-defmethod initialize-instance :AFTER ((this mlint-lm-entry-deprecated) +(cl-defmethod initialize-instance :after ((this mlint-lm-entry-deprecated) &rest fields) "Calculate the 'new text' for THIS instance. Optional argument FIELDS are the initialization arguments." @@ -548,6 +551,28 @@ Optional argument FIELDS are the initialization arguments." newfcn)) )) +(defclass mlint-lm-function-name (mlint-lm-replace-focus) + () + "When function name is missmatched with the file name." + ) + +(cl-defmethod initialize-instance :after ((this mlint-lm-function-name) &rest fields) + "Compute the 'new text' for THIS to be the file name from the message. +Optional arguments FIELDS are the initialization arguments." + (let* ((warn (oref this warning)) + (junk (or (string-match "file name: '\\([a-zA-z][a-zA-z0-9]+\\)'" warn) + (string-match "do not agree: '\\([a-zA-z][a-zA-z0-9]+\\)'" warn)) + ) + (newfcn (when junk (match-string 1 warn)))) + (oset this new-text newfcn) + ;; After basic initialization, update the fix description. + (oset this fix-description + (concat (oref-default mlint-lm-replace-focus fix-description) + newfcn)) + )) + +;;; Custom auto-fix entries +;; (defclass mlint-lm-entry-logicals (mlint-lm-entry) ((fixable-p :initform t) (fix-description :initform "perform a replacement.") @@ -600,6 +625,32 @@ Optional argument FIELDS are the initialization arguments." (insert ";")) ) +(defclass mlint-lm-missing-end (mlint-lm-entry) + ((fixable-p :initform t) + (fix-description :initform "Add matching end for this line.")) + "Missing end with guess as to where it might go." + ) + +(cl-defmethod mlint-fix-entry ((ent mlint-lm-missing-end)) + "Add semi-colon to end of this line." + (save-excursion + (let* ((msg (oref ent warning)) + line blockname) + ;; Extract info about this. + (when (string-match "(after line \\([0-9]+\\))" msg) + (setq line (match-string 1 msg))) + (when (string-match "possibly matching \\([A-Z]+\\)\\." msg) + (setq blockname (match-string 1 msg))) + + ;; Did we get the right kind of warning + (when (and line blockname) + (mlint-goto-line (string-to-number line)) + + ;; add the end and indent + (indent-region (point) (save-excursion (insert "end\n") (point))) + ) + ))) + ;;; User functions ;; (defun mlint-highlight (err) From a8a7b7e73c75452037869d06025ad18959d51d62 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 5 Mar 2021 16:42:43 -0500 Subject: [PATCH 014/168] mlint.el: (mlint-error-id-fix-alist): Add ENDCT support. (mlint-lm-missing-end::mlint-fix-entry): Add support for adding end to functions without ends. --- mlint.el | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/mlint.el b/mlint.el index c28fe08..cde55d2 100644 --- a/mlint.el +++ b/mlint.el @@ -190,6 +190,7 @@ be cause for being turned off in a buffer." ( NOCOM . mlint-lm-delete-focus ) ( ST2NM . mlint-lm-str2num ) ( FDEPR . mlint-lm-entry-deprecated ) + ( ENDCT . mlint-lm-missing-end ) ( ENDCT2 . mlint-lm-missing-end ) ( FNDEF . mlint-lm-function-name ) ( MCFIL . mlint-lm-function-name ) @@ -643,12 +644,32 @@ Optional arguments FIELDS are the initialization arguments." (setq blockname (match-string 1 msg))) ;; Did we get the right kind of warning - (when (and line blockname) - (mlint-goto-line (string-to-number line)) - - ;; add the end and indent - (indent-region (point) (save-excursion (insert "end\n") (point))) - ) + (if line + ;; We have a line number, just go for it there. + (progn + (mlint-goto-line (string-to-number line)) + ;; add the end and indent + (indent-region (point) (save-excursion (insert "end\n") (point))) + ) + (if (and blockname (string= blockname "FUNCTION")) + ;; It is a function, but no line number. Let's guess where this end + ;; should go. + (save-excursion + (mlint-goto-line (oref ent line)) ;; go to the fcn + (end-of-line) + (if (re-search-forward "^function " nil t) + (progn + (beginning-of-line) + ;; skip over comments that might be headers to the found function. + (matlab-find-prev-code-line) + (forward-line 1) + (save-excursion (insert "end\n\n")) + (matlab-indent-line)) + (goto-char (point-max)) + (save-excursion (insert "\nend\n\n")) + (matlab-indent-line)))) + ) + )) ))) ;;; User functions From 204fd79f1b563dd5a0019bc9dc0c2e400ab174a1 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 5 Mar 2021 16:59:06 -0500 Subject: [PATCH 015/168] matlab.el: (matlab-valid-end-construct-p): an 'end' after a . is a field, and not a valid block end. (matlab-cursor-on-valid-block-start): any block after a . is a field, and not a valid block start. mlint.el: (mlint-lm-missing-end::mlint-fix-entry): Fix too many close ) tests/blocks.m: Add struct_stuff method to verify not indenting anything after a . tests/stringtest.m: Add an invalid block comment (all on one line) with test markers in it. --- matlab.el | 70 +++++++++++++++++++++++++--------------------- mlint.el | 2 +- tests/blocks.m | 12 ++++++++ tests/stringtest.m | 2 ++ 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/matlab.el b/matlab.el index d01a5fa..b25786b 100644 --- a/matlab.el +++ b/matlab.el @@ -1795,38 +1795,41 @@ restricted." Return nil if it is being used to dereference an array." (let ((p (point)) (err1 t)) - (condition-case nil - (save-match-data - (matlab-with-current-command - ;; This used to add some sort of protection, but I don't know what - ;; the condition was, or why the simple case doesn't handle it. - ;; - ;; The above replacement fixes a case where a continuation in an array - ;; befuddles the identifier. - ;; (progn ;;(matlab-end-of-command (point)) - ;; (end-of-line) - ;; (if (> p (point)) - ;; (progn - ;; (setq err1 nil) - ;; (error))) - ;; (point)))) - (save-excursion - ;; beginning of param list - (matlab-up-list -1) - ;; backup over the parens. If that fails - (condition-case nil - (progn - (forward-sexp 1) - ;; If we get here, the END is inside parens, which is not a - ;; valid location for the END keyword. As such it is being - ;; used to dereference array parameters - nil) - ;; This error means that we have an unterminated paren - ;; block, so this end is currently invalid. - (error nil))))) - ;; an error means the list navigation failed, which also means we are - ;; at the top-level - (error err1)))) + (if (eq (preceding-char) ?.) + ;; This is a struct field, not valid. + nil + (condition-case nil + (save-match-data + (matlab-with-current-command + ;; This used to add some sort of protection, but I don't know what + ;; the condition was, or why the simple case doesn't handle it. + ;; + ;; The above replacement fixes a case where a continuation in an array + ;; befuddles the identifier. + ;; (progn ;;(matlab-end-of-command (point)) + ;; (end-of-line) + ;; (if (> p (point)) + ;; (progn + ;; (setq err1 nil) + ;; (error))) + ;; (point)))) + (save-excursion + ;; beginning of param list + (matlab-up-list -1) + ;; backup over the parens. If that fails + (condition-case nil + (progn + (forward-sexp 1) + ;; If we get here, the END is inside parens, which is not a + ;; valid location for the END keyword. As such it is being + ;; used to dereference array parameters + nil) + ;; This error means that we have an unterminated paren + ;; block, so this end is currently invalid. + (error nil))))) + ;; an error means the list navigation failed, which also means we are + ;; at the top-level + (error err1))))) ;;; Regexps for MATLAB language =============================================== @@ -2282,6 +2285,9 @@ Use this if you know what context you're in." ((not (looking-at (matlab-block-beg-re))) ;; Not looking at a valid block nil) + ((eq (preceding-char) ?.) + ;; Any block preceeded by a '.' is a field in a struct, and not valid. + nil) ;; Else, are we on a block that has special syntax? ((not (looking-at matlab-innerblock-syntax-re)) ;; Not an innerblock syntax that only work withing special blocks diff --git a/mlint.el b/mlint.el index cde55d2..4ad4115 100644 --- a/mlint.el +++ b/mlint.el @@ -670,7 +670,7 @@ Optional arguments FIELDS are the initialization arguments." (matlab-indent-line)))) ) )) - ))) + ) ;;; User functions ;; diff --git a/tests/blocks.m b/tests/blocks.m index 43b57c6..97226fe 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -143,6 +143,18 @@ classdef blocks < handle methods=obj.arguments(properties); %!!12 end %!!8 + function s=struct_stuff(~) %!!8 + + s.if = 1; %!!12 + s.else = 1.5; %!!12 + s.while = 2; %!!12 + s.switch = 3; %!!12 + s.case = 3.1; %!!12 + s.end = 5; %!!12 + + end %!!8 + + %!!8 end %<<14 diff --git a/tests/stringtest.m b/tests/stringtest.m index c239dcd..9a1f101 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -154,4 +154,6 @@ end not_commented(); +%{ just a regular comment #c# %} should_be_comment #c# + % Normal comment #c# From 66721488d995af45995ccae47eba941a4201b4f6 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 5 Mar 2021 20:09:22 -0500 Subject: [PATCH 016/168] tests/stringtest.m Added ellipsis test marks in some places where they were missing. --- tests/stringtest.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/stringtest.m b/tests/stringtest.m index 9a1f101..15d3d15 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -79,9 +79,9 @@ Ecv = 'string with ... in #v# it'; Es = "string with ... in #s# it"; % Comment with ... in it #c# -x = [ 'foo bar',newline,... - ' ''-goo'', ... #v#',newline,... - ' ''-bar'', ... #v#',newline]; +x = [ 'foo bar', newline, ... #e# + ' ''-goo'', ... #v#', newline, ... #e# + ' ''-bar'', ... #v#', newline ]; func_call1('function with charvec', ... #e# 'after ellipsis charvec with ellipsis ... #v#'); From 1b0340b26b82e036fc619008e570a6e893c1532d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 6 Mar 2021 13:05:29 -0500 Subject: [PATCH 017/168] matlab.el: (matlab-lattr-block-close): Account for commend imediately after the end keyword. tests/blocks.m: (tightcomments): Add sample with comments tight to end of every statement with indentation test cookies. --- matlab.el | 5 ++++- tests/blocks.m | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/matlab.el b/matlab.el index b25786b..813a713 100644 --- a/matlab.el +++ b/matlab.el @@ -2869,7 +2869,10 @@ Argument START is where to start searching from." (goto-char (point-max)) ;; If in a comment, move out of it first. - (matlab-backward-up-string-or-comment) + (when (matlab-backward-up-string-or-comment) + ;; in case of no space between comment and end, need to move back + ;; over the comment chart for next search to work. + (forward-char 1)) ;; Count every END in the line, skipping over active blocks (while (re-search-backward (concat "\\<" (matlab-block-end-re) "\\>") diff --git a/tests/blocks.m b/tests/blocks.m index 97226fe..ea6c704 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -153,8 +153,15 @@ classdef blocks < handle s.end = 5; %!!12 end %!!8 - - + + function tightcomments%!!8 + if condition%!!12 + switch thing%!!16 + case b%!!18 + end%!!16 + end%!!12 + end%!!8 + %!!8 end %<<14 From 4baad2b86c3526f4e89665cdcf1c0870d7064a27 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 6 Mar 2021 23:56:15 -0500 Subject: [PATCH 018/168] matlab-syntax.el: New file - manage syntax table, and utilities that use the syntax table for parsing strings, comments, and lists. --- matlab-syntax.el | 467 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 matlab-syntax.el diff --git a/matlab-syntax.el b/matlab-syntax.el new file mode 100644 index 0000000..8d48667 --- /dev/null +++ b/matlab-syntax.el @@ -0,0 +1,467 @@ +;;; matlab-syntax.el --- Manage MATLAB syntax tables and buffer parsing. +;; +;; Copyright (C) 2021 Eric Ludlam +;; +;; Author: +;; +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see https://www.gnu.org/licenses/. + +;;; Commentary: +;; +;; Manage syntax handling for `matlab-mode'. +;; Matlab's syntax for comments and strings can't be handled by a standard +;; Emacs syntax table. This code handles the syntax table, and special +;; scanning needed to augment a buffer's syntax for all our special cases. +;; +;; This file also handles all the special parsing needed to support indentation, +;; block scanning, and the line. + +(require 'matlab-compat) + +;;; Code: + +(defvar matlab-syntax-table + (let ((st (make-syntax-table (standard-syntax-table)))) + ;; Comment Handling: + ;; Multiline comments: %{ text %} + ;; Single line comments: % text (single char start) + ;; Ellipsis omments: ... text (comment char is 1st char after 3rd dot) + ;; ^ handled in `matlab--syntax-propertize' + (modify-syntax-entry ?% "< 13" st) + (modify-syntax-entry ?{ "(} 2c" st) + (modify-syntax-entry ?} "){ 4c" st) + (modify-syntax-entry ?\n ">" st) + + ;; String Handling: + ;; Character vector: 'text' + ;; String: "text" + ;; These next syntaxes are handled with `matlab--syntax-propertize' + ;; Transpose: varname' + ;; Quoted quotes: ' don''t ' or " this "" " + ;; Unterminated Char V: ' text + (modify-syntax-entry ?' "\"" st) + (modify-syntax-entry ?\" "\"" st) + + ;; Words and Symbols: + (modify-syntax-entry ?_ "_" st) + + ;; Punctuation: + (modify-syntax-entry ?\\ "." st) + (modify-syntax-entry ?\t " " st) + (modify-syntax-entry ?+ "." st) + (modify-syntax-entry ?- "." st) + (modify-syntax-entry ?* "." st) + (modify-syntax-entry ?/ "." st) + (modify-syntax-entry ?= "." st) + (modify-syntax-entry ?< "." st) + (modify-syntax-entry ?> "." st) + (modify-syntax-entry ?& "." st) + (modify-syntax-entry ?| "." st) + + ;; Parentheticl blocks: + ;; Note: these are in standard syntax table, repeated here for completeness. + (modify-syntax-entry ?\( "()" st) + (modify-syntax-entry ?\) ")(" st) + (modify-syntax-entry ?\[ "(]" st) + (modify-syntax-entry ?\] ")[" st) + ;;(modify-syntax-entry ?{ "(}" st) - Handled as part of comments + ;;(modify-syntax-entry ?} "){" st) + + st) + "MATLAB syntax table") + +(defvar matlab-navigation-syntax-table + (let ((st (copy-syntax-table matlab-syntax-table))) + ;; Make _ a part of words so we can skip them better + (modify-syntax-entry ?_ "w" st) + st) + "The syntax table used when navigating blocks.") + +(defmacro matlab-navigation-syntax (&rest forms) + "Set the current environment for syntax-navigation and execute FORMS." + (declare (indent 0)) + (list 'let '((oldsyntax (syntax-table)) + (case-fold-search nil)) + (list 'unwind-protect + (list 'progn + '(set-syntax-table matlab-navigation-syntax-table) + (cons 'progn forms)) + '(set-syntax-table oldsyntax)))) + +(add-hook 'edebug-setup-hook + (lambda () + (def-edebug-spec matlab-navigation-syntax def-body))) + +;;; Buffer Scanning for Syntax Table Augmentation +;; +;; To support all our special syntaxes via syntax-ppss (parse partial +;; sexp), we need to scan the buffer for patterns, and then leave +;; behind the hints pps needs to do the right thing. +;; +;; Support is broken up in these functions: +;; * matlab--put-char-category - Apply a syntax category to a character +;; * matlab--syntax-symbol - Create a syntax category symbol +;; * matlab--syntax-propertize - Used as `syntax-propertize-function' for +;; doing the buffer scan to augment syntxes. +;; * matlab--scan-line-* - Scan for specific types of syntax occurances. + +(defun matlab--put-char-category (pos category) + "At character POS, put text CATEGORY." + (put-text-property pos (1+ pos) 'category category) + (put-text-property pos (1+ pos) 'mcm t) + ) + +(defmacro matlab--syntax-symbol (symbol syntax doc) + "Create a new SYMBOL used as a text property category with SYNTAX." + (declare (indent defun)) + `(progn (defvar ,symbol ,syntax ,doc) + (set ',symbol ,syntax) ;; So you can re-eval it. + (put ',symbol 'syntax-table ,symbol) + )) + +(matlab--syntax-symbol matlab--unterminated-string-syntax '(15 . nil) ;; Generic string end + "Syntax placed on end-of-line for unterminated strings.") +(put 'matlab--unterminated-string-syntax 'unterminated t) ;; Font-lock cookie + +(matlab--syntax-symbol matlab--ellipsis-syntax (string-to-syntax "< ") ;; comment char + "Syntax placed on ellipsis to treat them as comments.") + +(matlab--syntax-symbol matlab--not-block-comment-syntax (string-to-syntax "(}") ;; Just a regular open brace + "Syntax placed on ellipsis to treat them as comments.") + +(defun matlab--syntax-propertize (&optional start end) + "Scan region between START and END for unterminated strings. +Only scans whole-lines, as MATLAB is a line-based language. +If region is not specified, scan the whole buffer. +See `matlab--scan-line-for-ellipsis', `matlab--san-line-bad-blockcomment', +and `matlab--scan-line-for-unterminated-string' for specific details." + (save-match-data ;; avoid 'Syntax Checking transmuted the match-data' + (save-excursion + ;; Scan region, but always expand to beginning of line + (goto-char (or start (point-min))) + (beginning-of-line) + ;; Clear old properties + (remove-text-properties (point) (save-excursion (goto-char (or end (point-max))) + (end-of-line) (point)) + '(category nil mcm nil)) + ;; Apply properties + (while (and (not (>= (point) (or end (point-max)))) (not (eobp))) + + ;; Multiple ellipsis can be on a line. Find them all + (beginning-of-line) + (while (matlab--scan-line-for-ellipsis) + ;; Mark ellipsis as if a comment. + (matlab--put-char-category (point) 'matlab--ellipsis-syntax) + (forward-char 3) + ) + + ;; Multiple invalid block comment starts possible. Find them all + (beginning-of-line) + (while (matlab--scan-line-bad-blockcomment) + ;; Mark 2nd char as just open brace, not punctuation. + (matlab--put-char-category (point) 'matlab--not-block-comment-syntax) + ) + + ;; Look for an unterminated string. Only one possible per line. + (beginning-of-line) + (when (matlab--scan-line-for-unterminated-string) + ;; Mark this one char plus EOL as end of string. + (let ((start (point))) + (matlab--put-char-category (point) 'matlab--unterminated-string-syntax) + (end-of-line) + (matlab--put-char-category (point) 'matlab--unterminated-string-syntax) + )) + + (beginning-of-line) + (forward-line 1)) + ))) + +(matlab--syntax-symbol matlab--transpose-syntax '(3 . nil) ;; 3 = symbol + "Treat ' as non-string when used as transpose.") + +(matlab--syntax-symbol matlab--quoted-string-syntax '(9 . nil) ;; 9 = escape in a string + "Treat '' or \"\" as not string delimeteres when inside a string.") + +(defun matlab--scan-line-for-unterminated-string (&optional debug) + "Scan this line for an unterminated string, leave cursor on starting string char." + ;; First, scan over all the string chars. + (save-restriction + (narrow-to-region (point-at-bol) (point-at-eol)) + (beginning-of-line) + (condition-case err + (while (re-search-forward "\\s\"\\|\\s<" nil t) + (let ((start-str (match-string 0)) + (start-char (match-beginning 0))) + (forward-char -1) + (if (looking-at "\\s<") + (forward-comment 1) + ;; Else, check for valid string + (if (or (bolp) + (string= start-str "\"") + (save-excursion + (forward-char -1) + (not (looking-at "\\(\\w\\|\\s_\\|\\s)\\|\"\\|\\.\\)")))) + (progn + ;; Valid string start, try to skip the string + (forward-sexp 1) + ;; If we just finished and we have a double of ourselves, + ;; convert those doubles into punctuation. + (when (looking-at start-str) + (forward-char -1) + (matlab--put-char-category (point) 'matlab--quoted-string-syntax) + ;; and try again. + (goto-char start-char) + )) + (when (string= start-str "'") + ;; If it isn't valid string, it's just transpose or something. + ;; convert to a symbol - as a VAR'', the second ' needs to think it + ;; is not after punctuation. + (matlab--put-char-category (point) 'matlab--transpose-syntax)) + ;; Move forward 1. + (forward-char 1) + ))) + nil) + (error + t)))) + +(defun matlab--scan-line-bad-blockcomment () + "Scan this line for invalid block comment starts." + (save-restriction + (narrow-to-region (point-at-bol) (point-at-eol)) + (when (and (re-search-forward "%{" nil t) (not (looking-at "\\s-*$"))) + (goto-char (1- (match-end 0))) + t))) + + +(defun matlab--scan-line-for-ellipsis () + "Scan this line for an ellipsis." + (save-restriction + (narrow-to-region (point-at-bol) (point-at-eol)) + (when (re-search-forward "\\.\\.\\." nil t) + (goto-char (match-beginning 0)) + t))) + +;;; Font Lock Support: +;; +;; The syntax specific font-lock support handles comments and strings. +;; +;; We'd like to support multiple kinds of strings and comments. To do +;; that we overload `font-lock-syntactic-face-function' with our own. +;; This does the same job as the orriginal, except we scan the start +;; for special cookies left behind by `matlab--syntax-propertize' and +;; use that to choose different fonts. +(defun matlab--font-lock-syntactic-face (pps) + "Return the face to use for the syntax specified in PPS." + ;; From the default in font-lock. + ;; (if (nth 3 state) font-lock-string-face font-lock-comment-face) + (if (nth 3 pps) + ;; This is a string. Check the start char to see if it was + ;; marked as an unterminate string. + (if (get-text-property (nth 8 pps) 'unterminated) + matlab-unterminated-string-face + font-lock-string-face) + font-lock-comment-face) + ) + +;;; +;;; EXPERIMENTAL INTEGRATION: +;;; +;; +;; Connect our special logic into a running MATLAB Mode +;; replacing existing mechanics. +;; +;; Delete this if/when it becomes a permanent part of `matlab-mode'. + +(defun matlab-syntax-setup () + "Integrate our syntax handling into a running `matlab-mode' buffer. +Safe to use in `matlab-mode-hook'." + ;; Syntax Table support + (set-syntax-table matlab-syntax-table) + (make-local-variable 'syntax-propertize-function) + (setq syntax-propertize-function 'matlab--syntax-propertize) + ;; Comment handlers + (make-local-variable 'comment-start) + (make-local-variable 'comment-end) + (make-local-variable 'comment-start-skip) + (make-local-variable 'page-delimiter) + (setq comment-start "%" + comment-end "" + comment-start-skip "%\\s-+" + page-delimiter "^\\(\f\\|%%\\(\\s-\\|\n\\)\\)") + ;; Other special regexps handling different kinds of syntax. + (make-local-variable 'paragraph-start) + (setq paragraph-start (concat "^$\\|" page-delimiter)) + (make-local-variable 'paragraph-separate) + (setq paragraph-separate paragraph-start) + (make-local-variable 'paragraph-ignore-fill-prefix) + (setq paragraph-ignore-fill-prefix t) + (make-local-variable 'page-delimiter) + (setq page-delimiter "^\\(\f\\|%%\\(\\s-\\|\n\\)\\)") + + ;; Font lock + (make-local-variable 'font-lock-syntactic-face-function) + (setq font-lock-syntactic-face-function 'matlab--font-lock-syntactic-face) + ) + +;;; Syntax Testing for Strings and Comments +;; +;; These functions detect syntactic context based on the syntax table. +(defun matlab-cursor-comment-string-context (&optional bounds-sym) + "Return the comment/string context of cursor for the current line. +Return 'comment if in a comment. +Return 'string if in a string. +Return 'charvector if in a character vector +Return 'ellipsis if after an ... ellipsis +Return nil if none of the above. +Scans from the beginning of line to determine the context. +If optional BOUNDS-SYM is specified, set that symbol value to the +bounds of the string or comment the cursor is in" + (let* ((pps (syntax-ppss (point))) + (start (nth 8 pps)) + (end 0) + (syntax nil)) + ;; Else, inside something if 'start' is set. + (when start + (save-match-data + (save-excursion + (goto-char start) ;; Prep for extra checks. + (setq syntax + (cond ((eq (nth 3 pps) t) + (if (= (following-char) ?') + 'charvector + 'string)) + ((eq (nth 3 pps) ?') + 'charvector) + ((eq (nth 3 pps) ?\") + 'string) + ((nth 4 pps) + (if (= (following-char) ?\%) + 'comment + 'ellipsis)) + (t nil))) + + ;; compute the bounds + (when (and syntax bounds-sym) + (if (memq syntax '(charvector string)) + ;;(forward-sexp 1) - overridden - need primitive version + (goto-char (scan-sexps (point) 1)) + (forward-comment 1) + (if (bolp) (forward-char -1))) + (set bounds-sym (list start (point)))) + ))) + + ;; Return the syntax + syntax)) + +(defun matlab-cursor-in-string-or-comment () + "Return t if the cursor is in a valid MATLAB comment or string." + ;; comment and string depend on each other. Here is one test + ;; that does both. + (when (matlab-cursor-comment-string-context) t)) + +(defun matlab-cursor-in-comment () + "Return t if the cursor is in a valid MATLAB comment." + (eq (matlab-cursor-comment-string-context) 'comment)) + +(defun matlab-cursor-in-string (&optional incomplete) + "Return t if the cursor is in a valid MATLAB character vector or string scalar. +Note: INCOMPLETE is now obsolete +If the optional argument INCOMPLETE is non-nil, then return t if we +are in what could be a an incomplete string. (Note: this is also the default)" + (let ((ctxt (matlab-cursor-comment-string-context))) + (or (eq ctxt 'string) (eq ctxt 'charvector)))) + +;;; Block Comment handling +;; +;; Old version block comments were handled in a special way. +;; Can we simplify with syntax tables? +(defconst matlab-block-comment-start-re "^\\s-*%{\\s-*$" + "Regexp that matches the beginning of a block comment. +Block comment indicators must be on a line by themselves.") + +(defconst matlab-block-comment-end-re "^\\s-*%}\\s-*$" + "Regexp that matches the end of a block comment. +Block comment indicators must be on a line by themselves.") + +(defun matlab-ltype-block-comment-start () + "Return non-nil if the current line is a block comment start." + (save-excursion + (beginning-of-line) + (looking-at matlab-block-comment-start-re))) + +(defun matlab-ltype-block-comment-end () + "Return non-nil if the current line is a block comment start." + (save-excursion + (beginning-of-line) + (looking-at matlab-block-comment-end-re))) + +(defun matlab-block-comment-bounds (&optional linebounds) + "Return start and end positions of block comment if we are in one. +Optional LINEBOUNDS specifies if returned limits are line based instead +of character based." + (let* ((pps (syntax-ppss (point))) + (start (nth 8 pps)) + (end 0)) + ;; 4 is comment flag. 7 is '2' if block comment + (when (and (nth 4 pps) (eq (nth 7 pps) 2)) + (save-excursion + (goto-char start) + (forward-comment 1) + (setq end (point))) + (if linebounds + ;; Bounds expanded to beginning/end of the line + (cons (save-excursion + (goto-char start) + (point-at-bol)) + (save-excursion + (goto-char end) + (point-at-eol))) + ;; Just the bounds + (cons start end))))) + +;;; Navigating Lists +;; +;; MATLAB's lists are (), {}, []. +;; We used to need to do special stuff, but now I think this +;; is just a call striaght to up-list. + +(defun matlab-up-list (count &optional restrict) + "Move forwards or backwards up a list by COUNT. +Optional argument RESTRICT is ignored." + (save-restriction + (when restrict (narrow-to-region restrict (point))) + (let ((dir (if (> count 0) 1 -1))) + (while (/= count 0) + (up-list dir nil nil) + (unless (matlab-cursor-in-string-or-comment) + ;; Only count this as moving up a list if we aren't + ;; in a comment. + (setq count (- count dir))) + )))) + +;;; Syntax Compat functions +;; +;; Left over old APIs. Delete these someday. +(defun matlab-move-simple-sexp-backward-internal (count) + "Move backward COUNT number of MATLAB sexps." + (forward-sexp (- count))) + +(defun matlab-move-simple-sexp-internal(count) + "Move over one MATLAB sexp COUNT times. +If COUNT is negative, travel backward." + (forward-sexp count)) + +(provide 'matlab-syntax) + +;;; matlab-syntax.el ends here From f0457f228b8f785d8c2829e1b055024d766de92e Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 00:14:21 -0500 Subject: [PATCH 019/168] matlab.el: Delete large swaths of code related to syntax tables which has moved to matlab-syntax.el. (matlab-syntax): New require. (matlab-mode-syntax-table, matlab-mode-special-syntax-table): Moved to matlab-syntax.el (matlab-font-lock-string-start-regexp) (matlab-font-lock-string-and-comment-start-regexp) (matlab-test-allstring-comment-match) (matlab-font-lock-allstring-comment-match-normal): No longer needed. Deleted. (matlab-font-lock-extend-region): Switch to use matlab-block-comment-bounds. (matlab-block-comment-*-re, matlab-ltype-block-comment-*): Moved to matlab-syntax.el (matlab-basic-font-lock-keywords): Delete comment and string highlighting. (matlab-basic-file-font-lock-keywords): Delete block comment highlighting. (matlab-mode): Delete all code related to syntax stuff. Add call to `matlab-syntax-setup'. Remove `after-change-functions' - no longer needed to handle block comments. Enable syntax table font-locking comments/strings. Remove special forward-sexp-function. (matlab-navigation-syntax, matlab-up-list): Moved to matlab-syntax.el (matlab-with-current-command): Extend restricted region 1 char past eol to include newline - needed for end-of-comment. (matlab-backward-up-string-or-comment): Fix ellipsis spelling. Only backup 1 char, reverse scan goes to beginning, not end of ... (matlab-move-list-sexp-internal) (matlab-move-simple-sexp-backward-internal) (matlab-move-simple-sexp-internal) Deleted. (matlab-backward-sexp, matlab-forward-sexp) (matlab-beginning-of-command, matlab-ltype-comm) (matlab-debug-block-comm) (matlab-electric-block-comment) (matlab-show-paren-or-block) (matlab-mode-vf-block-matches-forward): Rename matlab-ltype-block-comm to matlab-block-comment-bounds. (matlab-ltype-block-comm-bounds, matlab-ltype-block-comm-lastcompute) (matlab-change-funtion, matlab-ltype-block-comm, matlab-ltype-block-comm-1): Deleted. (matlab-ltype-block-comm-at-*) (matlab-cursor-comment-string-context) (matlab-cursor-in-string-or-comment) (matlab-cursor-in-comment) (matlab-cursor-in-string): Moved to matlab-syntax.el (matlab-calculate-indentation-1): Replace old block comment stuff with new `matlab-block-comment-bounds'. Stop using old cache, just compute bounds once and use it. tests/mtest.el: Rename matlab-ltype-block-comm to matlab-block-comment-bounds. Fix spelling of ellipsis Add a little more debugging output. Makefile, Project.ede: Add matlab-syntax.el tests/stringtest.m: Add some blank lines to protect against indentation. Fix indentation. --- Makefile | 2 +- Project.ede | 2 +- matlab.el | 610 ++++----------------------------------------- tests/metest.el | 9 +- tests/stringtest.m | 15 +- 5 files changed, 63 insertions(+), 575 deletions(-) diff --git a/Makefile b/Makefile index 45604a3..a743bc0 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ LOADPATH= ./ LOADDEFS=matlab-load.el LOADDIRS=. misc_MISC=ChangeLog ChangeLog.old1 ChangeLog.old2 INSTALL README dl_emacs_support.m -lisp_LISP=matlab-compat.el matlab.el matlab-shell.el matlab-shell-gud.el matlab-netshell.el matlab-complete.el matlab-cgen.el matlab-publish.el matlab-topic.el mlint.el tlc.el linemark.el matlab-maint.el +lisp_LISP=matlab-compat.el matlab-syntax.el matlab.el matlab-shell.el matlab-shell-gud.el matlab-netshell.el matlab-complete.el matlab-cgen.el matlab-publish.el matlab-topic.el mlint.el tlc.el linemark.el matlab-maint.el cedet_LISP=semantic-matlab.el semanticdb-matlab.el srecode-matlab.el cedet-matlab.el company-matlab-shell.el VERSION=4.0 DISTDIR=$(top)matlab-emacs-$(VERSION) diff --git a/Project.ede b/Project.ede index 688ae06..a006c7b 100644 --- a/Project.ede +++ b/Project.ede @@ -17,7 +17,7 @@ (ede-proj-target-elisp "ede-proj-target-elisp" :name "lisp" :path "" - :source '("matlab-compat.el" "matlab.el" "matlab-shell.el" "matlab-shell-gud.el" "matlab-netshell.el" "matlab-complete.el" "matlab-cgen.el" "matlab-publish.el" "matlab-topic.el" "mlint.el" "tlc.el" "linemark.el" "matlab-maint.el") + :source '("matlab-compat.el" "matlab-syntax.el" "matlab.el" "matlab-shell.el" "matlab-shell-gud.el" "matlab-netshell.el" "matlab-complete.el" "matlab-cgen.el" "matlab-publish.el" "matlab-topic.el" "mlint.el" "tlc.el" "linemark.el" "matlab-maint.el") :versionsource '("matlab.el") :aux-packages '("matlab")) (ede-proj-target-elisp "ede-proj-target-elisp" diff --git a/matlab.el b/matlab.el index 813a713..5b9758c 100644 --- a/matlab.el +++ b/matlab.el @@ -51,6 +51,7 @@ ;;; Code: (require 'matlab-compat) +(require 'matlab-syntax) (require 'easymenu) (require 'derived) @@ -558,35 +559,6 @@ If font lock is not loaded, lay in wait." ;;; MATLAB mode variables ===================================================== -;; syntax table -(defvar matlab-mode-syntax-table - (let ((st (make-syntax-table (standard-syntax-table)))) - (modify-syntax-entry ?_ "_" st) - (modify-syntax-entry ?% "<" st) - (modify-syntax-entry ?\n ">" st) - (modify-syntax-entry ?\\ "." st) - (modify-syntax-entry ?\t " " st) - (modify-syntax-entry ?+ "." st) - (modify-syntax-entry ?- "." st) - (modify-syntax-entry ?* "." st) - (modify-syntax-entry ?' "." st) - (modify-syntax-entry ?\" "." st) - (modify-syntax-entry ?/ "." st) - (modify-syntax-entry ?= "." st) - (modify-syntax-entry ?< "." st) - (modify-syntax-entry ?> "." st) - (modify-syntax-entry ?& "." st) - (modify-syntax-entry ?| "." st) - st) - "The syntax table used in `matlab-mode' buffers.") - -(defvar matlab-mode-special-syntax-table - (let ((st (copy-syntax-table matlab-mode-syntax-table))) - ;; Make _ a part of words so we can skip them better - (modify-syntax-entry ?_ "w" st) - st) - "The syntax table used when navigating blocks.") - ;; abbrev table (defvar matlab-mode-abbrev-table nil "The abbrev table used in `matlab-mode' buffers.") @@ -823,94 +795,6 @@ If font lock is not loaded, lay in wait." The ' character has restrictions on what starts a string which is needed when attempting to understand the current context.") -(defvar matlab-font-lock-string-start-regexp (concat matlab-string-start-regexp "\\(['\"]\\)") - "Regexp used by font lock to find the beginning of a char vector or string scalar.") - -(defvar matlab-font-lock-string-and-comment-start-regexp (concat matlab-font-lock-string-start-regexp - "\\|\\(%\\)\\|\\(\\.\\.\\.\\)") - "Starting matcher for allstring comment font lock.") - -(defun matlab-test-allstring-comment-match () - "Text command for the allstring font locker." - (interactive) - ;(beginning-of-line) - (matlab-font-lock-allstring-comment-match-normal (point-max)) - (goto-char (match-beginning 0)) - (call-interactively 'set-mark-command) - (goto-char (match-end 0))) - -(defun matlab-font-lock-allstring-comment-match-normal (limit) - "When font locking strings, call this function for normal character vectors. -Argument LIMIT is the maximum distance to scan." - (when (and (< (point) limit) - (re-search-forward matlab-font-lock-string-and-comment-start-regexp - (if (eq limit (point-max)) - (- limit 1) ;; consider case of "..." at EOF - limit) - t)) - - ;; We might match a comment, string or unterminated string. - (let ((strchar (preceding-char)) - (b0 (or (match-beginning 2) (match-beginning 3) (match-end 4))) - (bs nil) - (es nil) - (bu nil) - (eu nil) - (bc nil) - (ec nil) - (done nil) - (searchlim (point-at-eol))) - - ;; Identify the thing we just found, and do different things based on that. - ;; - - ;; Comments and elipsis go to the end of the line, making this part simple - (if (or (eq strchar ?%) (eq strchar ?.)) - (progn - (setq bc b0 - ec (point-at-eol)) - ) - - ;; Not a comment, must be a string or charvec - - ;; Scan to end of string by looking at every matching - ;; string character and deciding what it means. - (while (and (not done) - (re-search-forward "['\"]" searchlim t)) - (goto-char (match-end 0)) - (if (eq (preceding-char) strchar) - ;; Same type of string - (if (eq (following-char) strchar) - ;; This is a quoted quote. Skip it and keep going. - (forward-char 1) - ;; solo quote, end of string - (setq bs b0 - es (point) - done t)) - ;; The other type of string - just keep going. - nil)) - - ;; If not done, unterminated - (if (not done) - (setq bu b0 - eu searchlim)) - ) - - ;; Fake out some match data - (set-match-data - (list - b0 (or es eu ec) - bs es ; matched string - bu eu ; unterminated string - bc ec ; comment - )) - - ;; Move to the end - (goto-char (or es eu ec)) - - ;; Successful string - t))) - ;;; Font Lock Comment and Unreachable Code Matchers ;; (defvar font-lock-beg) (defvar font-lock-end) ; quiet compiler. @@ -919,7 +803,7 @@ Argument LIMIT is the maximum distance to scan." "Called by font-lock to extend the region if we are in a multi-line block." ;; Only deal with block comments for now. - (let* ((pos (matlab-ltype-block-comm t)) + (let* ((pos (matlab-block-comment-bounds t)) (flb font-lock-beg) (fle font-lock-end)) (when pos @@ -934,27 +818,6 @@ Argument LIMIT is the maximum distance to scan." ;; We made a change t))) -(defconst matlab-block-comment-start-re "^\\s-*%{\\s-*$" - "Regexp that matches the beginning of a block comment. -Block comment indicators must be on a line by themselves.") - -(defun matlab-ltype-block-comment-start () - "Return non-nil if the current line is a block comment start." - (save-excursion - (beginning-of-line) - (looking-at matlab-block-comment-start-re))) - -(defconst matlab-block-comment-end-re "^\\s-*%}\\s-*$" - "Regexp that matches the end of a block comment. -Block comment indicators must be on a line by themselves.") - -(defun matlab-ltype-block-comment-end () - "Return non-nil if the current line is a block comment start." - (save-excursion - (beginning-of-line) - (looking-at matlab-block-comment-end-re))) - - (defun matlab-find-block-comments (limit) "Find code that is commented out with %{ until %}. Argument LIMIT is the maximum distance to search." @@ -1180,12 +1043,6 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (defconst matlab-basic-font-lock-keywords (list - ;; charvec and string quote chars are also used as transpose, but only if directly - ;; after characters, numbers, underscores, or closing delimiters. - '(matlab-font-lock-allstring-comment-match-normal - (1 font-lock-string-face nil t) - (2 matlab-unterminated-string-face nil t) - (3 font-lock-comment-face nil t)) ;; Various pragmas should be in different colors. ;; I think pragmas are always lower case? '("%#\\([a-z]+\\)" (1 'bold prepend)) @@ -1232,11 +1089,11 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (4 font-lock-comment-face prepend) ;commented out part. ) ;; block comments need to be commented out too! - '(matlab-find-block-comments - (1 font-lock-comment-face prepend) ; commented out - (2 'underline prepend) - (3 'underline prepend) ;the comment parts - ) + ;;'(matlab-find-block-comments + ;; (1 font-lock-comment-face prepend) ; commented out + ;; (2 'underline prepend) + ;; (3 'underline prepend) ;the comment parts + ;; ) ;; Cell mode breaks get special treatment '("^\\s-*\\(%%[^\n]*\n\\)" (1 matlab-cellbreak-face append)) ;; Highlight cross function variables @@ -1518,59 +1375,61 @@ All Key Bindings: (if (boundp 'whitespace-modes) (add-to-list 'whitespace-modes 'matlab-mode)) (setq local-abbrev-table matlab-mode-abbrev-table) - (set-syntax-table matlab-mode-syntax-table) + + ;; Syntax tables and related features are in matlab-syntax.el + ;; This includes syntax table definitions, misc syntax regexps + ;; and font-lock for comments/strings. + (matlab-syntax-setup) + + ;; Indentation setup. (setq indent-tabs-mode nil) (make-local-variable 'indent-line-function) (setq indent-line-function 'matlab-indent-line) - (make-local-variable 'paragraph-start) - (setq paragraph-start (concat "^$\\|" page-delimiter)) - (make-local-variable 'paragraph-separate) - (setq paragraph-separate paragraph-start) - (make-local-variable 'paragraph-ignore-fill-prefix) - (setq paragraph-ignore-fill-prefix t) - (make-local-variable 'comment-start-skip) - (setq comment-start-skip "%\\s-+") - (make-local-variable 'comment-start) - (setq comment-start "%") - (make-local-variable 'page-delimiter) - (setq page-delimiter "^\\(\f\\|%%\\(\\s-\\|\n\\)\\)") (make-local-variable 'comment-column) (setq comment-column matlab-comment-column) (make-local-variable 'comment-indent-function) (setq comment-indent-function 'matlab-comment-indent) + (make-local-variable 'add-log-current-defun-function) (setq add-log-current-defun-function 'matlab-current-defun) - (make-local-variable 'fill-column) - (setq fill-column matlab-fill-column) ;; Emacs 20 supports this variable. ;; This lets users turn auto-fill on and off and still get the right ;; fill function. (make-local-variable 'normal-auto-fill-function) (setq normal-auto-fill-function 'matlab-auto-fill) + (make-local-variable 'fill-column) + (setq fill-column matlab-fill-column) (make-local-variable 'fill-paragraph-function) (setq fill-paragraph-function 'matlab-fill-paragraph) (make-local-variable 'fill-prefix) (make-local-variable 'imenu-generic-expression) (setq imenu-generic-expression matlab-imenu-generic-expression) + ;; Save hook for verifying src. This lets us change the name of ;; the function in `write-file' and have the change be saved. ;; It also lets us fix mistakes before a `save-and-go'. (make-local-variable 'write-contents-functions) (add-hook 'write-contents-functions 'matlab-mode-verify-fix-file-fn) + + ;; DELETE ;; when a buffer changes, flush parsing data. - (add-hook 'after-change-functions 'matlab-change-function nil t) + ;;(add-hook 'after-change-functions 'matlab-change-function nil t) + ;; give each file it's own parameter history (make-local-variable 'matlab-shell-save-and-go-history) + + ;; Font lock support: (make-local-variable 'font-lock-defaults) (setq font-lock-defaults '((matlab-file-font-lock-keywords matlab-file-gaudy-font-lock-keywords matlab-file-really-gaudy-font-lock-keywords ) - t ; do not do string/comment highlighting + nil ; matlab-syntax supports comments and strings. nil ; keywords are case sensitive. ;; This puts _ as a word constituent, ;; simplifying our keywords significantly ((?_ . "w")))) + (setq font-lock-multiline 'undecided) (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) @@ -1586,10 +1445,6 @@ All Key Bindings: (if window-system (matlab-frame-init)) - ;; built-in sexp navigation - (make-local-variable 'forward-sexp-function) - (setq forward-sexp-function #'matlab-move-simple-sexp-internal) - ;; If first function is terminated with an end statement, then functions have ;; ends. (if (matlab-do-functions-have-end-p) @@ -1717,58 +1572,6 @@ Ignore comments and whitespace." (setq lst (cdr lst))) (nreverse nlst))) -; Aki Vehtari recommends this: (19.29 required) -;(require 'backquote) -;(defmacro matlab-navigation-syntax (&rest body) -; "Evaluate BODY with the matlab-mode-special-syntax-table" -; '(let ((oldsyntax (syntax-table))) -; (unwind-protect -; (progn -; (set-syntax-table matlab-mode-special-syntax-table) -; ,@body) -; (set-syntax-table oldsyntax)))) - -(defmacro matlab-navigation-syntax (&rest forms) - "Set the current environment for syntax-navigation and execute FORMS." - (list 'let '((oldsyntax (syntax-table)) - (case-fold-search nil)) - (list 'unwind-protect - (list 'progn - '(set-syntax-table matlab-mode-special-syntax-table) - (cons 'progn forms)) - '(set-syntax-table oldsyntax)))) - -(put 'matlab-navigation-syntax 'lisp-indent-function 0) -(add-hook 'edebug-setup-hook - (lambda () - (def-edebug-spec matlab-navigation-syntax def-body))) - -(defun matlab-up-list (count &optional restrict) - "Move forwards or backwards up a list by COUNT. -Optional argument RESTRICT is where to restrict the search." - ;; MATLAB syntax table has no disabling strings or comments. - (let ((dir (if (> 0 count) -1 +1)) - (origin (point)) - (ms nil)) - ;; Make count positive - (setq count (* count dir)) - (if (= dir -1) - (while (/= count 0) - ;; Search till we find an unstrung paren object. - (setq ms (re-search-backward "\\s(\\|\\s)" restrict t)) - (while (and (save-match-data (matlab-cursor-in-string-or-comment)) - (setq ms (re-search-backward "\\s(\\|\\s)" restrict t)))) - (if (not ms) - (progn - (goto-char origin) - (error "Scan Error: List mismatch"))) - ;; View it's match. - (let ((s (match-string 0))) - (if (string-match "\\s(" s) - (setq count (1- count)) - (setq count (1+ count))))) - (error "Not implemented")) - ms)) (defvar matlab-in-command-restriction nil "Non-nil if currently in a `matlab-with-current-command' form.") @@ -1785,7 +1588,7 @@ restricted." (matlab-beginning-of-command) (beginning-of-line) (point)) - (matlab-point-at-eol))) + (1+ (matlab-point-at-eol)))) (let ((matlab-in-command-restriction t)) ,@forms ))) @@ -2009,122 +1812,10 @@ Returns new location if the cursor is moved. nil otherwise." (goto-char (nth 0 bounds)) (unless (bobp) (when (eq ctxt 'comment) (forward-char -1)) - (when (eq ctxt 'elipsis) (forward-char -3))) + (when (eq ctxt 'ellipsis) (forward-char -1))) t))) -(defun matlab-move-list-sexp-internal (dir) - "Move over one MATLAB list sexp in direction DIR. -Only covers list sexp. If not adjacent to a list, do nothing." - (let ((depth 1) - (sc (if (> dir 0) #'skip-chars-forward #'skip-chars-backward)) - (fc (if (> dir 0) #'forward-char #'backward-char)) - (rx (if (> dir 0) #'re-search-forward #'re-search-backward)) - (start (point)) - (udir (if (> dir 0) 1 -1)) - (match nil)) - (funcall sc " \t\n") - (funcall fc 1) - (while (and (> depth 0) (funcall rx "\\s(\\|\\s)" nil t)) ; look up next paren thing - (let* ((bounds nil) - (ctxt (matlab-cursor-comment-string-context 'bounds))) - (if ctxt - ;; do nothing if found paren in a string or comment, but do move out of it. - (goto-char (if (< dir 0) (nth 0 bounds) (nth 1 bounds))) - ;; We are in code somewhere, so decide if we have move into or out of our list. - (setq match (match-string 0)) - (cond ((string-match "\\s(" match) - (setq depth (+ depth udir)) - ) - ((string-match "\\s)" match) - (setq depth (- depth udir)) - ))))) - (when (> depth 0) - (goto-char (point)) - (error "Unbalanced Parenthesis")) - )) - -(defun matlab-move-simple-sexp-backward-internal (count) - "Move backward COUNT number of MATLAB sexps." - (interactive "P") - (unless count (setq count 1)) - (matlab-move-simple-sexp-internal (- count))) - -(defun matlab-move-simple-sexp-internal(count) - "Move over one MATLAB sexp COUNT times. -If COUNT is negative, travel backward." - (interactive "P") - (unless (eq count 0) - (unless count (setq count 1)) - ;; Get base whitespace out of the way first. - (if (> 0 count) - (skip-chars-backward " \t;.") - (skip-chars-forward " \t;.")) - - (let* ((bounds nil) - (ctxt (matlab-cursor-comment-string-context 'bounds)) - (skipnav nil)) - (when (and ctxt (not (eolp)) (not (looking-at "%"))) - ;; First, if we are IN a string or comment then navigate differently. - - ;; To start, if we are at the EDGE of the comment or string, skip - ;; over it so we can keep going. - (cond ((and (> 0 count) (< (- (point) (car bounds)) 3)) - ;; Skip out backward - (goto-char (car bounds))) - ((and (< 0 count) (< (- (nth 1 bounds) (point)) 2)) - ;; skip out forward - (goto-char (nth 1 bounds))) - (t - ;; Nav over regular words inside. - (save-restriction - (narrow-to-region (car bounds) (nth 1 bounds)) - (forward-word count)) - (when (eq (point) (car bounds)) - (forward-char (if (> 0 count) -1 1))) - (setq skipnav t) - ))) - - (unless skipnav - ;; Outside of comments and strings, look at our local syntax and decide what to do. - ;; Skip over whitespace to see what the next interesting thing is. - (while (not (= 0 count)) - (if (< 0 count) - (progn ;; forward motion - (skip-chars-forward " \t\n;.=") - - (cond ((or (looking-at "['\"]\\|%\\|\\.\\.\\.")) - ;; In a comment or string. - (forward-char 1) - (matlab-up-string-or-comment)) - - ((looking-at "\\s(") - ;; At the beginning of a list, matrix, cell, whatever. - (matlab-move-list-sexp-internal count)) - (t - (forward-symbol 1)) - ) - (setq count (1- count)) - ) - - ;; backward motion - (skip-chars-backward " \t\n;.=") - (let ((ctxt2 (matlab-cursor-comment-string-context))) - - (cond ((or (looking-back "['\"]" (- (point) 2)) - (and (eolp) (or (eq ctxt2 'comment) (eq ctxt2 'elipsis)))) - (backward-char 2) - (matlab-backward-up-string-or-comment)) - - ((looking-back "\\s)" (- (point) 1)) - ;; At the end of a list, matrix, cell, etc - (matlab-move-list-sexp-internal count)) - (t - (forward-symbol -1)) - )) - (setq count (1+ count)) - )) - )))) (defun matlab-backward-sexp (&optional autoend noerror) "Go backwards one balanced set of MATLAB expressions. @@ -2146,7 +1837,7 @@ This assumes that expressions do not cross \"function\" at the left margin." (point))))) (if bcend (goto-char bcend)))) - (let ((bc (matlab-ltype-block-comm))) + (let ((bc (matlab-block-comment-bounds))) (goto-char (car bc))) ) ;; Not auto-end, and not looking @ and end type keyword @@ -2214,7 +1905,7 @@ a valid context." ((and (not autostart) (matlab-ltype-block-comment-start)) (goto-char (match-end 0)) - (let ((bc (matlab-ltype-block-comm))) + (let ((bc (matlab-block-comment-bounds))) (when bc (goto-char (cdr bc)))) ) ;; No autostart, and looking at a block keyword. @@ -2507,7 +2198,7 @@ Travels across continuations." (re-search-backward "^\\s-*\\(%\\|if\\|else\\(if\\)\\|while\\|\\(par\\)?for\\|$\\)\\>" nil t))) - (bc (matlab-ltype-block-comm))) + (bc (matlab-block-comment-bounds))) (if bc ;; block comment - just go to the beginning. (goto-char (car bc)) @@ -2547,22 +2238,6 @@ Optional BEGINNING is where the command starts from." ;;; Line types, attributes, and string/comment context ================================================= -(defvar matlab-ltype-block-comm-bounds nil - "Bounds of the last block comment detected. -The bounds returned in this form: - (START END)") -(defvar matlab-ltype-block-comm-lastcompute nil - "Location of the last computation for block comments.") - - -(defun matlab-change-function (beg end length) - "Function run after a buffer is modified. -BEG, END, and LENGTH are unused." - ;; Flush block comment parsing info since those - ;; locations change on buffer edit. - (setq matlab-ltype-block-comm-bounds nil - matlab-ltype-block-comm-lastcompute nil) - ) (defun matlab-ltype-empty () ; blank line "Return t if current line is empty." @@ -2585,7 +2260,7 @@ Return the symbol 'blockcomm if we are in a block comment." (save-excursion (beginning-of-line) (cond - ((matlab-ltype-block-comm) + ((matlab-block-comment-bounds) 'blockcomm) ((matlab-ltype-comm-noblock) t) @@ -2616,93 +2291,11 @@ Return the symbol 'blockcomm if we are in a block comment." (defun matlab-debug-block-comm () "Test block comment detector since font-lock won't let us debug." (interactive) - (let ((pos (matlab-ltype-block-comm t))) + (let ((pos (matlab-block-comment-bounds t))) (if pos (pulse-momentary-highlight-region (car pos) (cdr pos)) (message "No block comment.")))) -(defun matlab-ltype-block-comm (&optional linebounds) - "Return start positions of block comment if we are in a block comment. -Optional LINEBOUNDS specifies if returned limits are line based instead -of character based." - (let ((bounds matlab-ltype-block-comm-bounds) - (lcbounds matlab-ltype-block-comm-lastcompute)) - - ;;(if bounds (message "Recycle bounds") (message "no recycle bounds")) - - (cond ((and bounds (>= (point) (car bounds)) - (<= (point) (cdr bounds))) - ;; All set! - nil) - - ((and lcbounds (>= (point) (car lcbounds)) - (<= (point) (cdr lcbounds))) - ;; Also all set - nil) - - (t - (setq bounds (matlab-ltype-block-comm-1))) - ) - - (if (not bounds) - - ;; cache point location - (setq matlab-ltype-block-comm-lastcompute - (cons (point-at-bol) (point-at-eol))) - - ;; Else clear lastcompute location - (setq matlab-ltype-block-comm-lastcompute nil) - ;; Check if caller wants line bounds. - (when (and linebounds bounds) - (save-excursion - (goto-char (car bounds)) - (setcar bounds (point-at-bol)) - (goto-char (cdr bounds)) - (setcdr bounds (point-at-eol))))) - - bounds)) - -(defun matlab-ltype-block-comm-1 () - "Return the start positions of block comment if we are in a block comment." - (save-match-data - (save-excursion - (let ((start nil) - (good t) - (end nil)) - (if (and (matlab-ltype-block-comment-start) - (not (matlab-cursor-in-string-or-comment))) - (setq start (match-beginning 0)) ;; CHECK - - (while (and (setq good (re-search-backward "^\\s-*\\%\\([{}]\\)\\s-*$" nil t)) - (matlab-cursor-in-string-or-comment)) - nil) - - (when (and good (matlab-ltype-block-comment-start)) - (setq start (match-beginning 0)))) - - (when start - (while (and (setq good (re-search-forward matlab-block-comment-end-re nil t)) - (matlab-cursor-in-string t)) - nil) - - (if (and good (goto-char (match-beginning 0)) (matlab-ltype-block-comment-end)) - (setq end (match-end 0)) - (setq end (point-max)))) - - (if (and start end) - (setq matlab-ltype-block-comm-bounds (cons start end)) - (setq matlab-ltype-block-comm-bounds nil)))))) - -(defun matlab-ltype-block-comm-at-start () - "Return non-nil if we are on a block comment start line AND -the %{ is the only non-whitespace text on the line." - (matlab-ltype-block-comment-start)) - -(defun matlab-ltype-block-comm-at-end () - "Return non-nil if we are on a block comment end line AND -the %{ is the only non-whitespace text on this line." - (matlab-ltype-block-comment-end)) - (defun matlab-ltype-continued-comm () "Return column of previous line's comment start, or nil." @@ -2961,118 +2554,6 @@ line." )))) -(defun matlab-cursor-comment-string-context (&optional bounds-sym) - "Return the comment/string context of cursor for the current line. -Return 'comment if in a comment. -Return 'string if in a string. -Return 'charvector if in a character vector -Return 'elipsis if after an ... elipsis -Return nil if none of the above. -Scans from the beginning of line to determine the context. -If optional BOUNDS-SYM is specified, set that symbol value to the -bounds of the string or comment the cursor is in" - (save-match-data - (save-restriction - (narrow-to-region (matlab-point-at-bol) (matlab-point-at-eol)) - (let ((p (point)) - (returnme nil) - (sregex (concat "\\(%\\)\\|" matlab-string-start-regexp "\\('\\|\"\\)\\|\\(\\.\\.\\.\\)")) - (insregex "\\('\\|\"\\)") - (laststart nil)) - (save-excursion - (goto-char (point-min)) - (while (and (not (eq returnme 'comment)) - (not (eq returnme 'elipsis)) - (re-search-forward (if (not returnme) sregex insregex) nil t) - (<= (point) p)) - (cond ((eq returnme nil) - (cond ((= (preceding-char) ?%) - (setq returnme 'comment)) - ((= (preceding-char) ?.) - (setq returnme 'elipsis)) - ((= (preceding-char) ?') - (setq returnme 'charvector)) - ((= (preceding-char) ?\") - (setq returnme 'string))) - (setq laststart (1- (point))) ; store location - ) - ;; ((eq returnme comment) % not possible) - ((eq returnme 'string) - (cond ((= (preceding-char) ?%) nil) ; ok - ((= (preceding-char) ?') nil) ; ok - ((= (preceding-char) ?\") - (if (looking-at "\"") - (forward-char 1) ; skip quoted quote - (setq returnme nil ; end of string - laststart nil) - )))) - ((eq returnme 'charvector) - (cond ((= (preceding-char) ?%) nil) ; ok - ((= (preceding-char) ?\") nil) ; ok - ((= (preceding-char) ?') - (if (looking-at "'") - (forward-char 1) ; skip quoted quote - (setq returnme nil ; end of charvec - laststart nil)) - ))) - (t (message "Bug in `matlab-cursor-comment-string-context'"))))) - - ;; If we want to get the bounds of the string or comment the cursor is in - ;; then we need to find the end of whatever it is. - (when (and bounds-sym laststart) - (if (or (eq returnme 'comment) (eq returnme 'elipsis)) - ;; Comments and elipsis always end at end of line. - (set bounds-sym (list laststart (point-at-eol))) - - ;; Strings/charvec we need to keep searching forward. - (save-excursion - (let ((done nil) - (searchlim (point-at-eol)) - (strchar (cond ((eq returnme 'charvector) ?') - ((eq returnme 'string) ?\") - (t (error "Bug in matlab-cursor-comment-string-context"))))) - (save-match-data - (while (and (not done) - (re-search-forward "['\"]" searchlim t)) - (goto-char (match-end 0)) - (if (eq (preceding-char) strchar) - ;; Same type of string - (if (eq (following-char) strchar) - ;; This is a quoted quote. Skip it and keep going. - (forward-char 1) - ;; solo quote, end of string - (set bounds-sym (list laststart (point))) - (setq done t)) - ;; The other type of string - just keep going. - nil))) - (when (not done) - (set bounds-sym (list laststart (point-at-eol)))) - )))) - - ;; Return the identified context. - returnme)))) - -(defun matlab-cursor-in-string-or-comment () - "Return t if the cursor is in a valid MATLAB comment or string." - ;; comment and string depend on each other. Here is one test - ;; that does both. - (let ((ctxt (matlab-cursor-comment-string-context))) - (if (not ctxt) - nil - t))) - -(defun matlab-cursor-in-comment () - "Return t if the cursor is in a valid MATLAB comment." - (let ((ctxt (matlab-cursor-comment-string-context))) - (eq ctxt 'comment))) - -(defun matlab-cursor-in-string (&optional incomplete) - "Return t if the cursor is in a valid MATLAB character vector or string scalar. -Note: INCOMPLETE is now obsolete -If the optional argument INCOMPLETE is non-nil, then return t if we -are in what could be a an incomplete string. (Note: this is also the default)" - (let ((ctxt (matlab-cursor-comment-string-context))) - (or (eq ctxt 'string) (eq ctxt 'charvector)))) (defun matlab-comment-on-line () "Place the cursor on the beginning of a valid comment on this line. @@ -3140,6 +2621,7 @@ this line's indentation should be. See `matlab-next-line-indentation'." "Do the indentation work of `matlab-calculate-indentation'. Argument CURRENT-INDENTATION is what the previous line recommends for indentation." (let ((ci current-indentation) + (blockcomm nil) (tmp nil)) (cond ;; COMMENTS @@ -3157,23 +2639,23 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (matlab-beginning-of-defun) (current-indentation))))) ;; BLOCK COMMENT END _or_ body prefixed with % - ((and matlab-ltype-block-comm-bounds - (or (matlab-ltype-block-comm-at-end) + ((and (setq blockcomm (matlab-block-comment-bounds)) + (or (matlab-ltype-block-comment-end) (matlab-ltype-comm-noblock))) (list 'comment (save-excursion - (goto-char (car matlab-ltype-block-comm-bounds)) + (goto-char (car blockcomm)) (current-indentation))) ) ;; BLOCK COMMENT START - ((and matlab-ltype-block-comm-bounds - (matlab-ltype-block-comm-at-start)) + ((and blockcomm + (matlab-ltype-block-comment-start)) (list 'comment (+ ci matlab-comment-anti-indent)) ) ;; BLOCK COMMENT BODY. - (matlab-ltype-block-comm-bounds + (blockcomm (list 'comment (+ (save-excursion - (goto-char (car matlab-ltype-block-comm-bounds)) + (goto-char (car blockcomm)) (current-indentation)) 2)) ) @@ -3590,7 +3072,7 @@ Argument ARG specifies how many %s to insert." Argument ARG specifies how many %s to insert." (interactive "P") (self-insert-command (or arg 1)) - (let ((bc (save-excursion (beginning-of-line) (matlab-ltype-block-comm)))) + (let ((bc (save-excursion (beginning-of-line) (matlab-block-comment-bounds)))) (cond ((matlab-ltype-block-comment-start) @@ -3598,7 +3080,7 @@ Argument ARG specifies how many %s to insert." ;; comment, and blink it if a problem. (let ((bcwrapped (save-excursion (beginning-of-line) - (matlab-ltype-block-comm)))) + (matlab-block-comment-bounds)))) ;; Regardless, indent our line (matlab-indent-line) @@ -3612,7 +3094,7 @@ Argument ARG specifies how many %s to insert." )) ;;ELSE, maybe end of block comment - ((and bc (matlab-ltype-block-comm-at-end)) + ((and bc (matlab-ltype-block-comment-end)) (progn (matlab-indent-line) ;; The above sometimes puts the cursor on the %, not after it. @@ -4131,7 +3613,7 @@ INTERACTIVE is ignored." Highlights parens and if/end type blocks. Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" (unless (or (matlab-cursor-in-string-or-comment) ; Only do this if not in a string. - (matlab-ltype-block-comm)) + (matlab-block-comment-bounds)) (save-match-data (save-excursion (let ((here-beg nil) @@ -4613,7 +4095,7 @@ by `matlab-mode-vf-add-ends'" e) (condition-case nil (if (and (not (matlab-cursor-in-string-or-comment)) - (not (matlab-ltype-block-comm)) + (not (matlab-block-comment-bounds)) (or matlab-functions-have-end (not (looking-at "function")))) (progn (matlab-forward-sexp) diff --git a/tests/metest.el b/tests/metest.el index 213f667..19f85fd 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -64,22 +64,23 @@ (goto-char (match-end 1)) (let ((md (match-data)) (mc (match-string 1)) - (bc (matlab-ltype-block-comm)) + (bc (matlab-block-comment-bounds)) (qd (matlab-cursor-comment-string-context))) ;; Test 1 - what are we? (unless (or (and (string= "b" mc) bc) (and (string= "v" mc) (eq 'charvector qd)) (and (string= "s" mc) (eq 'string qd)) (and (string= "c" mc) (eq 'comment qd)) - (and (string= "e" mc) (eq 'elipsis qd)) + (and (string= "e" mc) (eq 'ellipsis qd)) ) - (error "Syntax Test Failure @ line %d: Expected %s but found %S" + (error "Syntax Test Failure @ line %d, char %d: Expected %s but found %S" (line-number-at-pos) + (point) (cond ((string= mc "b") "block comment") ((string= mc "v") "charvector") ((string= mc "s") "string") ((string= mc "c") "comment") - ((string= mc "e") "elipsis") + ((string= mc "e") "ellipsis") (t "unknown test token")) qd)) ;; Test 2 - is match-data unchanged? diff --git a/tests/stringtest.m b/tests/stringtest.m index 15d3d15..4a0ebdf 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -69,19 +69,24 @@ icell_in_strs2_nested = { 'charv innercell " #v# }' "strinc innercel ' #s# }" %% Elipsis as comment fun_call(); ... This is a comment after an elipsis #e# -fun_call(); ... 'charvec in elipsis comment #e#' -fun_call(); ... "string in elipsis comment #e#" -fun_call(); ... % comment after an elipsis is still elipsis #e# +fun_call(); ... 'charvec in elipsis comment #e#' + +fun_call(); ... "string in elipsis comment #e#" + +fun_call(); ... % comment after an elipsis is still elipsis #e# + %% Elipsis and strings and other comments Ecv = 'string with ... in #v# it'; Es = "string with ... in #s# it"; % Comment with ... in it #c# +eecv = '...'; % string with only ellipsis in it #c# +ees = "..."; % string with only ellipsis in it #c# x = [ 'foo bar', newline, ... #e# - ' ''-goo'', ... #v#', newline, ... #e# - ' ''-bar'', ... #v#', newline ]; + ' ''-goo'', ... #v#', newline, ... #e# + ' ''-bar'', ... #v#', newline ]; func_call1('function with charvec', ... #e# 'after ellipsis charvec with ellipsis ... #v#'); From 6fe7a322d6dedb1fab5a49001159d359f53ea75a Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 10:50:16 -0500 Subject: [PATCH 020/168] matlab-syntax.el: (matlab-cursor-in-string-or-comment) (matlab-cursor-in-comment, matlab-cursor-in-string): Convert to use syntax-ppss directly. --- matlab-syntax.el | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 8d48667..1812e63 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -93,11 +93,11 @@ (declare (indent 0)) (list 'let '((oldsyntax (syntax-table)) (case-fold-search nil)) - (list 'unwind-protect - (list 'progn - '(set-syntax-table matlab-navigation-syntax-table) - (cons 'progn forms)) - '(set-syntax-table oldsyntax)))) + (list 'unwind-protect + (list 'progn + '(set-syntax-table matlab-navigation-syntax-table) + (cons 'progn forms)) + '(set-syntax-table oldsyntax)))) (add-hook 'edebug-setup-hook (lambda () @@ -242,7 +242,6 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (when (and (re-search-forward "%{" nil t) (not (looking-at "\\s-*$"))) (goto-char (1- (match-end 0))) t))) - (defun matlab--scan-line-for-ellipsis () "Scan this line for an ellipsis." @@ -317,6 +316,24 @@ Safe to use in `matlab-mode-hook'." ;;; Syntax Testing for Strings and Comments ;; ;; These functions detect syntactic context based on the syntax table. +(defun matlab-cursor-in-string-or-comment () + "Return non-nil if the cursor is in a valid MATLAB comment or string." + (let* ((pps (syntax-ppss (point)))) + (nth 8 pps))) + +(defun matlab-cursor-in-comment () + "Return t if the cursor is in a valid MATLAB comment." + (let* ((pps (syntax-ppss (point)))) + (nth 4 pps))) + +(defun matlab-cursor-in-string (&optional incomplete) + "Return t if the cursor is in a valid MATLAB character vector or string scalar. +Note: INCOMPLETE is now obsolete +If the optional argument INCOMPLETE is non-nil, then return t if we +are in what could be a an incomplete string. (Note: this is also the default)" + (let* ((pps (syntax-ppss (point)))) + (nth 3 pps))) + (defun matlab-cursor-comment-string-context (&optional bounds-sym) "Return the comment/string context of cursor for the current line. Return 'comment if in a comment. @@ -364,24 +381,6 @@ bounds of the string or comment the cursor is in" ;; Return the syntax syntax)) -(defun matlab-cursor-in-string-or-comment () - "Return t if the cursor is in a valid MATLAB comment or string." - ;; comment and string depend on each other. Here is one test - ;; that does both. - (when (matlab-cursor-comment-string-context) t)) - -(defun matlab-cursor-in-comment () - "Return t if the cursor is in a valid MATLAB comment." - (eq (matlab-cursor-comment-string-context) 'comment)) - -(defun matlab-cursor-in-string (&optional incomplete) - "Return t if the cursor is in a valid MATLAB character vector or string scalar. -Note: INCOMPLETE is now obsolete -If the optional argument INCOMPLETE is non-nil, then return t if we -are in what could be a an incomplete string. (Note: this is also the default)" - (let ((ctxt (matlab-cursor-comment-string-context))) - (or (eq ctxt 'string) (eq ctxt 'charvector)))) - ;;; Block Comment handling ;; ;; Old version block comments were handled in a special way. From 134800317a035019c1ec2fa26e7bc907342df09a Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 10:56:52 -0500 Subject: [PATCH 021/168] matlab.el: (matlab-lattr-block-close): After narrowing buffer, got to eol, not max - as we had to expand the narrowed region to handle special syntax chars on eol. metest.el: (metest-all-syntax-tests): Add ... (metest-end-detect-test): New test (met-end-dect-files): New Var (metest-comment-string-syntax-test) (metest-sexp-counting-test, metest-sexp-traversal-test) (metest-indents-test, metest-parse-test): Use new `metest-find-file' for loading in files. (met-indents-files): Add mfuncends.m tests/empty.m: tests/mfuncends.m: tests/mfuncnoend.m: New test files tests/stringtest.m: tests/mclass.m: Add end-detect expected value cookies. --- matlab.el | 2 +- tests/empty.m | 2 ++ tests/mclass.m | 1 + tests/metest.el | 53 ++++++++++++++++++++++++++++++++++++++++------ tests/mfuncends.m | 22 +++++++++++++++++++ tests/mfuncnoend.m | 15 +++++++++++++ tests/stringtest.m | 2 ++ 7 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 tests/empty.m create mode 100644 tests/mfuncends.m create mode 100644 tests/mfuncnoend.m diff --git a/matlab.el b/matlab.el index 5b9758c..1389b75 100644 --- a/matlab.el +++ b/matlab.el @@ -2459,7 +2459,7 @@ Argument START is where to start searching from." (save-excursion (when start (goto-char start)) (matlab-with-current-command - (goto-char (point-max)) + (goto-char (point-at-eol)) ;; If in a comment, move out of it first. (when (matlab-backward-up-string-or-comment) diff --git a/tests/empty.m b/tests/empty.m new file mode 100644 index 0000000..8f9eb09 --- /dev/null +++ b/tests/empty.m @@ -0,0 +1,2 @@ +% Empty M file. +% %%%empty guess \ No newline at end of file diff --git a/tests/mclass.m b/tests/mclass.m index a91ab85..f078fc2 100644 --- a/tests/mclass.m +++ b/tests/mclass.m @@ -1,6 +1,7 @@ % >>1 classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# % !!0 +% %%% class class % >>11 properties (Access='public') % #2# diff --git a/tests/metest.el b/tests/metest.el index 19f85fd..2074139 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -42,6 +42,7 @@ (defun metest-all-syntax-tests () "Run all the syntax tests in this file." + (metest-end-detect-test) (metest-comment-string-syntax-test) (metest-sexp-counting-test) (metest-sexp-traversal-test) @@ -49,17 +50,47 @@ (metest-parse-test) ) +(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncends.m" "mclass.m" ) + "List of files for running end detection tests on.") + +(defun metest-end-detect-test () + "Run a test to make sure we correctly detect the state of managing 'end'." + (dolist (F met-end-detect-files) + (let ((buf (metest-find-file F)) + (cnt 0)) + (with-current-buffer buf + (goto-char (point-min)) + (message ">> Checking END detection in %S" (current-buffer)) + (if (re-search-forward "%%%\\s-*\\(\\w+\\)\\s-+\\(\\w+\\)$" nil t) + (let ((st-expect (intern (match-string-no-properties 1))) + (end-expect (intern (match-string-no-properties 2))) + (st-actual (matlab-guess-script-type)) + (end-actual (matlab-do-functions-have-end-p))) + (unless (eq st-actual st-expect) + (error "Script type detection failure: Expected %s but found %s" + st-expect st-actual)) + (unless (eq end-actual end-expect) + (error "Script end detection failure: Expected %s but found %s" + end-expect end-actual)) + + (message "<< Script type and end detection passed: %s, %s" st-actual end-actual) + ) + ;; No expected values found in the file. + (error "Test file did not include expected script-type cookie") + )))) + (message "")) + (defvar met-stringtest-files '("stringtest.m") "List of files for running string tests on.") (defun metest-comment-string-syntax-test () "Run a test to make sure string nd comment highlighting work." (dolist (F met-stringtest-files) - (let ((buf (find-file-noselect (expand-file-name F met-testfile-path))) + (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) - (message ">> Starting search loop in %S" (current-buffer)) + (message ">> Starting string/comment detect loop in %S" (current-buffer)) (while (re-search-forward "#\\([csveb]\\)#" nil t) (goto-char (match-end 1)) (let ((md (match-data)) @@ -100,7 +131,7 @@ (defun metest-sexp-counting-test () "Run a test to make sure string and comment highlighting work." (dolist (F met-sexptest-files) - (let ((buf (find-file-noselect (expand-file-name F met-testfile-path))) + (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) @@ -140,7 +171,7 @@ (defun metest-sexp-traversal-test () "Run a test to make sure high level block navigation works." (dolist (F met-sexptest-files) - (let ((buf (find-file-noselect (expand-file-name F met-testfile-path))) + (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) @@ -173,13 +204,13 @@ (message "")) -(defvar met-indents-files '("indents.m" "mclass.m" "blocks.m") +(defvar met-indents-files '("indents.m" "mclass.m" "blocks.m" "mfuncends.m") "List of files for running syntactic indentation tests.") (defun metest-indents-test () "Run a test to make sure high level block navigation works." (dolist (F met-indents-files) - (let ((buf (find-file-noselect (expand-file-name F met-testfile-path))) + (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) @@ -207,7 +238,7 @@ "Run the semantic parsing test to make sure the parse works." (dolist (F met-parser-files) - (let ((buf (find-file-noselect (expand-file-name F met-testfile-path))) + (let ((buf (metest-find-file F)) exp act (cnt 0)) (with-current-buffer buf @@ -252,6 +283,14 @@ ) +(defun metest-find-file (file) + "Read FILE into a buffer and return it. +Do error checking to provide easier debugging." + (let ((F (expand-file-name file met-testfile-path))) + (unless (file-exists-p F) + (error "Test file %s does not exist in %s" file met-testfile-path)) + (find-file-noselect F))) + (provide 'metest) ;;; metest.el ends here diff --git a/tests/mfuncends.m b/tests/mfuncends.m new file mode 100644 index 0000000..4cf4542 --- /dev/null +++ b/tests/mfuncends.m @@ -0,0 +1,22 @@ +function mfuncends() +% Test function that has ends for each function. +% +% Used with test harness to validate indentation and end detection. +% +% %%%function function + + fcn_call(); %!!4 + + if condition %!!4 + fcn_call(); %!!8 + end %!!4 + +end%!!0 - also no space after end + +function fcn_call(inp) %!!0 + + while inp > 0 %!!4 + fcn_call(inp-1); %!!8 + end %!!4 + +end %!!0 diff --git a/tests/mfuncnoend.m b/tests/mfuncnoend.m new file mode 100644 index 0000000..027a0ab --- /dev/null +++ b/tests/mfuncnoend.m @@ -0,0 +1,15 @@ +function mfuncnoend +% A function file that does not have ends at the end of functions. +% +% %%% function nil + +fcn_call(1) + + +function fcn_call(idx) + +if idx > 0 + fcn_call(ix-1) +end + + \ No newline at end of file diff --git a/tests/stringtest.m b/tests/stringtest.m index 4a0ebdf..aec5e80 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -1,6 +1,8 @@ %% Tests for char vector and string handling. % % #c# +% +% %%%script script %% Basic strings From baa7c1210e61abfc65e26352f0db333bb726cdfd Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 10:58:19 -0500 Subject: [PATCH 022/168] tests/blocks.m: Remove mlint warnings. Full indent so blank-lines don't change on buffer indent. --- tests/blocks.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/blocks.m b/tests/blocks.m index ea6c704..6fe061e 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -151,17 +151,17 @@ classdef blocks < handle s.switch = 3; %!!12 s.case = 3.1; %!!12 s.end = 5; %!!12 - + end %!!8 - function tightcomments%!!8 + function tightcomments(~)%!!8 if condition%!!12 switch thing%!!16 case b%!!18 end%!!16 end%!!12 end%!!8 - + %!!8 end %<<14 From 8dafb13337db72a2e25a6b1e31c520b4949015c3 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 11:20:51 -0500 Subject: [PATCH 023/168] matlab-syntax.el (matlab-up-list): Replaced use of `up-list' when travelling backward. Instead use `syntax-ppss' and the list of paren positions to navigate upward while ignoring comments. When travelling forward, use plain up-list with no looping. --- matlab-syntax.el | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 1812e63..f916332 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -437,17 +437,29 @@ of character based." (defun matlab-up-list (count &optional restrict) "Move forwards or backwards up a list by COUNT. -Optional argument RESTRICT is ignored." +When travelling backward, use `syntax-ppss' counted paren +starts to navigate upward. +When travelling forward, use 'up-list' diretly, but disable +comment and string crossing. +Optional argument RESTRICT specifies max point to travel to." (save-restriction (when restrict (narrow-to-region restrict (point))) - (let ((dir (if (> count 0) 1 -1))) - (while (/= count 0) - (up-list dir nil nil) - (unless (matlab-cursor-in-string-or-comment) - ;; Only count this as moving up a list if we aren't - ;; in a comment. - (setq count (- count dir))) - )))) + (let* ((bounds nil) + (ctxt (matlab-cursor-comment-string-context 'bounds))) + (when ctxt + (goto-char (if (< 0 count) (car bounds) (cdr bounds)))) + (if (< count 0) + (let ((pps (syntax-ppss))) + (when (or (not (numberp (nth 0 pps))) + (< (nth 0 pps) (abs count))) + (error "Cannot navigate up %d lists" (abs count))) + ;; When travelling in reverse, we can just use pps' + ;; parsed paren list in slot 9. + (let ((posn (reverse (nth 9 pps)))) ;; Location of parens + (goto-char (nth (1- (abs count)) posn)))) + ;; Else - travel forward + (up-list count nil t)) ;; will this correctly ignore comments, etc? + ))) ;;; Syntax Compat functions ;; From 04ae9b124949caa1b5b5063164f1c1f9f3c38454 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 11:54:38 -0500 Subject: [PATCH 024/168] tests/metest.el: (metest-error): New function Adds file / lineno to test errors so you can jump to the right location quickly. (metest-*-test): Update all functions to use `metest-error'. --- tests/metest.el | 76 ++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/tests/metest.el b/tests/metest.el index 2074139..133475c 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -67,16 +67,16 @@ (st-actual (matlab-guess-script-type)) (end-actual (matlab-do-functions-have-end-p))) (unless (eq st-actual st-expect) - (error "Script type detection failure: Expected %s but found %s" - st-expect st-actual)) + (metest-error "Script type detection failure: Expected %s but found %s" + st-expect st-actual)) (unless (eq end-actual end-expect) - (error "Script end detection failure: Expected %s but found %s" - end-expect end-actual)) + (metest-error "Script end detection failure: Expected %s but found %s" + end-expect end-actual)) (message "<< Script type and end detection passed: %s, %s" st-actual end-actual) ) ;; No expected values found in the file. - (error "Test file did not include expected script-type cookie") + (metest-error "Test file did not include expected script-type cookie") )))) (message "")) @@ -104,19 +104,18 @@ (and (string= "c" mc) (eq 'comment qd)) (and (string= "e" mc) (eq 'ellipsis qd)) ) - (error "Syntax Test Failure @ line %d, char %d: Expected %s but found %S" - (line-number-at-pos) - (point) - (cond ((string= mc "b") "block comment") - ((string= mc "v") "charvector") - ((string= mc "s") "string") - ((string= mc "c") "comment") - ((string= mc "e") "ellipsis") - (t "unknown test token")) - qd)) + (metest-error "Syntax Test Failure @ char %d: Expected %s but found %S" + (point) + (cond ((string= mc "b") "block comment") + ((string= mc "v") "charvector") + ((string= mc "s") "string") + ((string= mc "c") "comment") + ((string= mc "e") "ellipsis") + (t "unknown test token")) + qd)) ;; Test 2 - is match-data unchanged? (unless (equal md (match-data)) - (error "Syntax checking transmuted the match data")) + (metest-error "Syntax checking transmuted the match data")) ;; Track (setq cnt (1+ cnt)) )) @@ -148,18 +147,16 @@ (if (not (eq (point) (point-min))) (save-restriction (widen) - (error "error at %d: Backward Sexp miscount tried %d, point %d, min %d" - (line-number-at-pos) - num (point) (point-at-bol)))) + (metest-error "Backward Sexp miscount tried %d, point %d, min %d" + num (point) (point-at-bol)))) (skip-chars-forward " \t;.=%") (matlab-move-simple-sexp-internal num) (skip-chars-forward " \t\n;.=%") (if (not (eq (point) (point-max))) (save-restriction (widen) - (error "Error at %d: Forward Sexp miscount tried %d, point %d, dest %d" - (line-number-at-pos) - num (point) (point-at-eol))))) + (metest-error "Forward Sexp miscount tried %d, point %d, dest %d" + num (point) (point-at-eol))))) )) (end-of-line) (setq cnt (1+ cnt)))) @@ -185,16 +182,16 @@ (matlab-forward-sexp) (skip-chars-forward " \n\t;%") (if (not (looking-at "<<\\([0-9]+\\)")) - (error "Error at %d: Failed to find matching test end token for %d" - (line-number-at-pos) num) + (metest-error "Failed to find matching test end token for %d" + num) (setq num2 (string-to-number (match-string 1))) (when (/= num num2) - (error "Error at %d: Failed to match correct test token. Start is %d, end is %d" - (line-number-at-pos) num num2))) + (metest-error "Failed to match correct test token. Start is %d, end is %d" + num num2))) (matlab-backward-sexp) (when (/= (point) begin) - (error "Error at %d: Failed to reverse navigate sexp for %d" - (line-number-at-pos) num)) + (metest-error "Failed to reverse navigate sexp for %d" + num)) ) (end-of-line) (setq cnt (1+ cnt)))) @@ -221,8 +218,8 @@ (calc (matlab-calc-indent)) (begin nil)) (when (not (= num calc)) - (error "Error at %d: Indentation found is %d, expected %d" - (line-number-at-pos) calc num)) + (metest-error "Indentation found is %d, expected %d" + calc num)) ) (end-of-line) (setq cnt (1+ cnt)))) @@ -252,9 +249,9 @@ (message ">> Starting semantic parser test in %S" (current-buffer)) (unless (re-search-forward "^%%\\s-*>>\\s-+SEMANTIC TEST" nil t) - (error "Semantic parser test: Failed to find test cookie.")) + (metest-error "Semantic parser test: Failed to find test cookie.")) (unless (re-search-forward "^%{[ \t\n]+\\(((\\)" nil t) - (error "Semantic parser test: Failed to find expected values.")) + (metest-error "Semantic parser test: Failed to find expected values.")) (goto-char (match-beginning 1)) (setq exp (read (buffer-substring (point) (save-excursion (re-search-forward "%}" nil t) @@ -264,12 +261,12 @@ ;; Compare the two lists ... simply. (while (and exp act) (unless (metest-compare-tags (car exp) (car act)) - (error "Expected tag %s, found %s" (semantic-format-tag-prototype (car exp)) - (semantic-format-tag-prototype (car act)))) + (metest-error "Expected tag %s, found %s" (semantic-format-tag-prototype (car exp)) + (semantic-format-tag-prototype (car act)))) (setq exp (cdr exp) act (cdr act) cnt (1+ cnt)) ) (when (or exp act) - (error "Found tags and expected tag lists differnet lengths.\nExpected Remains: %S\nActual Remains: %S" + (metest-error "Found tags and expected tag lists differnet lengths.\nExpected Remains: %S\nActual Remains: %S" exp act)) ) @@ -291,6 +288,15 @@ Do error checking to provide easier debugging." (error "Test file %s does not exist in %s" file met-testfile-path)) (find-file-noselect F))) +(defun metest-error (&rest args) + "Produce an err with standardized file/line prefix." + (declare (indent 1)) + (let ((pre (format "%s:%d: Error: " + (file-name-nondirectory (buffer-file-name)) + (line-number-at-pos))) + (post (apply 'format args))) + (error (concat pre post)))) + (provide 'metest) ;;; metest.el ends here From 6126f395481eb179c5faf2206d63dc2ab2bfd273 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 11:55:13 -0500 Subject: [PATCH 025/168] tests/indents.m: (array_constant-decls): Add a bunch of ways to have arrays be indented. --- tests/indents.m | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/indents.m b/tests/indents.m index 67b98ba..7290381 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -22,6 +22,7 @@ function indents(a,b,stuff) % !!4 block_starts_in_comments_and_strings(); + array_constant_decls(); % !!4 @@ -119,6 +120,25 @@ function B = ends_in_comments_and_strings() 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 + + out = { A %!!4 + Blong %!!12 + Csep %!!12 + }; %!!10 +end + function C = block_starts_in_comments_and_strings() % !!0 From 17cda9d9afcd7079d038a88109ffe6f2fad9d05a Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 14:06:01 -0500 Subject: [PATCH 026/168] matlab-cgen.el: Found usage of font-lock function that has been deleted. Mark this function as needing to be reimplemented. --- matlab-cgen.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/matlab-cgen.el b/matlab-cgen.el index b96b1bb..7111d46 100644 --- a/matlab-cgen.el +++ b/matlab-cgen.el @@ -234,14 +234,16 @@ the region. BEGIN and END mark the region to be stringified." "Spell check valid strings in region with Ispell. Argument BEGIN and END mark the region boundary." (interactive "r") + (error "This function needs to be reimplemented.") (require 'ispell) (save-excursion (goto-char begin) ;; Here we use the font lock function for finding strings. ;; Its cheap, fast, and accurate. ;; NOTE: This now also does comments - (while (and (matlab-font-lock-allstring-comment-match-normal end) - (ispell-region (match-beginning 0) (match-end 0)))))) + ;;(while (and (matlab-font-lock-allstring-comment-match-normal end) + ;; (ispell-region (match-beginning 0) (match-end 0)))) + )) (defun matlab-ispell-strings-and-comments () "Spell check valid strings in the current buffer with Ispell. From 83db249b9731ddde91e3316de45bebc7e67647e4 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 7 Mar 2021 14:09:41 -0500 Subject: [PATCH 027/168] matlab.el: (matlab-uniquify-list): Move to matlab-complete.el matlab-syntax.el: (matlb-beginning-of-string-or-comment): New nav function matlab-complete.el: (matlab-uniquify-list): Moved here from matlab.el (matlab-find-block-comments): Delete (matlab-file-basic-font-lock-keywords): Remove ref to above. (matlab-up-string-or-comment, matlab-backward-up-string-or-comment): Delete. (matlab-lattr-block-close): Replace use of matlab-backward-up-string-or-comment with matlab-beginning-of-string-or-comment. --- matlab-complete.el | 9 ++++ matlab-syntax.el | 7 ++++ matlab.el | 102 +++------------------------------------------ 3 files changed, 21 insertions(+), 97 deletions(-) diff --git a/matlab-complete.el b/matlab-complete.el index 97cfd22..e69af4e 100644 --- a/matlab-complete.el +++ b/matlab-complete.el @@ -31,6 +31,7 @@ ;;; Code: (require 'matlab) +(require 'matlab-shell) ;;; Customizations =========================================================== ;; @@ -183,6 +184,14 @@ This list still needs lots of help.") ;;; Completion Framework =================================================== ;; +(defun matlab-uniquify-list (lst) + "Return a list that is a subset of LST where all elements are unique." + (let ((nlst nil)) + (while lst + (if (and (car lst) (not (member (car lst) nlst))) + (setq nlst (cons (car lst) nlst))) + (setq lst (cdr lst))) + (nreverse nlst))) (defun matlab-find-recent-variable-list (prefix) "Return a list of most recent variables starting with PREFIX as a string. diff --git a/matlab-syntax.el b/matlab-syntax.el index f916332..53ddd78 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -381,6 +381,13 @@ bounds of the string or comment the cursor is in" ;; Return the syntax syntax)) +(defun matlab-beginning-of-string-or-comment () + "If the cursor is in a string or comment, move to the beginning. +Returns non-nil if the cursor moved." + (let* ((pps (syntax-ppss (point)))) + (when (nth 8 pps) (goto-char (nth 8 pps)) + ))) + ;;; Block Comment handling ;; ;; Old version block comments were handled in a special way. diff --git a/matlab.el b/matlab.el index 1389b75..fe9f7d7 100644 --- a/matlab.el +++ b/matlab.el @@ -818,52 +818,6 @@ when attempting to understand the current context.") ;; We made a change t))) -(defun matlab-find-block-comments (limit) - "Find code that is commented out with %{ until %}. -Argument LIMIT is the maximum distance to search." - (if (and (< (point) limit) - (re-search-forward matlab-block-comment-start-re limit t)) - (let ((b1 (match-beginning 0)) - (e1 (match-end 0)) - (b2 nil) (e2 nil) - (b3 nil) (e3 nil)) - (goto-char b1) - (if (and (not (bolp)) - (progn - (forward-char -1) - (matlab-cursor-in-string-or-comment))) - (progn - (goto-char e1) ;; skip over this one. - nil) - ;; Else, find the end. We will certainly be in - ;; a comment, so no need to check on the end. - (setq b2 (re-search-forward matlab-block-comment-end-re limit t)) - (if (not b2) - (progn - ;; No end ? Let's tell font-lock to just go - ;; to point-at-eol can call it done. - (goto-char e1) - (set-match-data - (list b1 (point-max) - b1 (point-max) - b1 e1 - (point-max) (point-max) - )) - (goto-char (point-max)) - t) - - ;; We have a match. Return that region. - (setq b2 (match-beginning 0) - e2 (match-end 0)) - (set-match-data - (list b1 e2 ; full match - b1 e2 ; the full comment - b1 e1 ; the block start - b2 e2 ; the block end - )) - (goto-char e2); move to end - t - ))))) (defun matlab-find-unreachable-code (limit) "Find code that is if'd out with if(0) or if(false), and mark it as a comment. @@ -1088,12 +1042,6 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (3 'underline prepend) ;else part (if applicable) (4 font-lock-comment-face prepend) ;commented out part. ) - ;; block comments need to be commented out too! - ;;'(matlab-find-block-comments - ;; (1 font-lock-comment-face prepend) ; commented out - ;; (2 'underline prepend) - ;; (3 'underline prepend) ;the comment parts - ;; ) ;; Cell mode breaks get special treatment '("^\\s-*\\(%%[^\n]*\n\\)" (1 matlab-cellbreak-face append)) ;; Highlight cross function variables @@ -1563,15 +1511,6 @@ Ignore comments and whitespace." (matlab-find-code-line)) ;; try again. t)) -(defun matlab-uniquify-list (lst) - "Return a list that is a subset of LST where all elements are unique." - (let ((nlst nil)) - (while lst - (if (and (car lst) (not (member (car lst) nlst))) - (setq nlst (cons (car lst) nlst))) - (setq lst (cdr lst))) - (nreverse nlst))) - (defvar matlab-in-command-restriction nil "Non-nil if currently in a `matlab-with-current-command' form.") @@ -1712,11 +1651,6 @@ Excludes function.") matlab-block-end-pre-no-if matlab-block-end-pre-if)) -;; Not used. -;;(defconst matlab-other-pre -;; "function\\|return" -;; "Partial regular express to recognize MATLAB non-block keywords.") - (defconst matlab-endless-blocks "case\\|otherwise" "Keywords which initialize new blocks, but don't have explicit ends. @@ -1790,33 +1724,6 @@ The class name is match 2." This is so the block highlighter doesn't gobble up lots of time when a block is not terminated.") -(defun matlab-up-string-or-comment () - "If the cursor is in a string or comment, move cursor to end of that syntax. -Returns new location if the cursor is moved. nil otherwise." - (interactive) - (let* ((bounds nil) - (ctxt (matlab-cursor-comment-string-context 'bounds))) - (when ctxt - (goto-char (nth 1 bounds)) - (unless (eobp) - (when (eq ctxt 'comment) (forward-char 1))) - t))) - -(defun matlab-backward-up-string-or-comment () - "If the cursor is in a string or comment, move cursor to beginning of that syntax. -Returns new location if the cursor is moved. nil otherwise." - (interactive) - (let* ((bounds nil) - (ctxt (matlab-cursor-comment-string-context 'bounds))) - (when ctxt - (goto-char (nth 0 bounds)) - (unless (bobp) - (when (eq ctxt 'comment) (forward-char -1)) - (when (eq ctxt 'ellipsis) (forward-char -1))) - t))) - - - (defun matlab-backward-sexp (&optional autoend noerror) "Go backwards one balanced set of MATLAB expressions. If optional AUTOEND, then pretend we are at an end. @@ -2462,10 +2369,11 @@ Argument START is where to start searching from." (goto-char (point-at-eol)) ;; If in a comment, move out of it first. - (when (matlab-backward-up-string-or-comment) + (when (matlab-beginning-of-string-or-comment) ;; in case of no space between comment and end, need to move back ;; over the comment chart for next search to work. - (forward-char 1)) + ;;(forward-char 1) + ) ;; Count every END in the line, skipping over active blocks (while (re-search-backward (concat "\\<" (matlab-block-end-re) "\\>") @@ -2473,8 +2381,8 @@ Argument START is where to start searching from." (let ((startmove (match-end 0)) (nomove (point))) (cond - ((matlab-backward-up-string-or-comment) - ;; Above returns t if it was in a string or comment. + ((matlab-beginning-of-string-or-comment) + ;; Above returns non-nil if it was in a string or comment. ;; In that case, we need to keep going. nil) ((not (matlab-valid-end-construct-p)) From b450fe1685ea510abda7d9350855b48b26ef0f1b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 8 Mar 2021 08:09:06 -0500 Subject: [PATCH 028/168] matlab.el: (matlab-ltype-comm): Make robust to block comment start, where bounds returns false when on start. Need to also check for the start of a block comment. (matlab--maybe-yes-or-no-p): New (matlab-calcuate-indentation-1): Use above instead of yes-or-no-p. metest.el: (met-end-detect-files): Add mfuncnoendblock.m mfuncends.m: Fix expected indentation numbers for continued arrays. mfuncnoendblock.m: New. --- matlab.el | 13 +++++++++++-- tests/metest.el | 2 +- tests/mfuncends.m | 8 ++++++-- tests/mfuncnoendblock.m | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 tests/mfuncnoendblock.m diff --git a/matlab.el b/matlab.el index fe9f7d7..452a955 100644 --- a/matlab.el +++ b/matlab.el @@ -2167,7 +2167,8 @@ Return the symbol 'blockcomm if we are in a block comment." (save-excursion (beginning-of-line) (cond - ((matlab-block-comment-bounds) + ((or (matlab-ltype-block-comment-start) + (matlab-block-comment-bounds)) 'blockcomm) ((matlab-ltype-comm-noblock) t) @@ -2525,6 +2526,14 @@ this line's indentation should be. See `matlab-next-line-indentation'." (matlab-navigation-syntax (matlab-calculate-indentation-1 current-indentation))) +(defun matlab--maybe-yes-or-no-p (prompt noninteractive-default) + "When in non-interactive mode run (yes-or-no-p prompt), +otherwise return NONINTERACTIVE-DEFAULT" + (if noninteractive + noninteractive-default + (yes-or-no-p prompt))) + + (defun matlab-calculate-indentation-1 (current-indentation) "Do the indentation work of `matlab-calculate-indentation'. Argument CURRENT-INDENTATION is what the previous line recommends for indentation." @@ -2597,7 +2606,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (matlab-ltype-function-definition))))) (if end-of-function (if (or matlab-functions-have-end - (if (yes-or-no-p matlab-functions-have-end-should-be-true) + (if (matlab--maybe-yes-or-no-p matlab-functions-have-end-should-be-true t) (matlab-functions-have-end-minor-mode 1) (error "Unmatched end"))) (if (matlab-indent-function-body-p) diff --git a/tests/metest.el b/tests/metest.el index 133475c..b592972 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -50,7 +50,7 @@ (metest-parse-test) ) -(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncends.m" "mclass.m" ) +(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" ) "List of files for running end detection tests on.") (defun metest-end-detect-test () diff --git a/tests/mfuncends.m b/tests/mfuncends.m index 4cf4542..deaf490 100644 --- a/tests/mfuncends.m +++ b/tests/mfuncends.m @@ -1,4 +1,4 @@ -function mfuncends() +function mfuncends( ) % Test function that has ends for each function. % % Used with test harness to validate indentation and end detection. @@ -9,14 +9,18 @@ function mfuncends() if condition %!!4 fcn_call(); %!!8 + fcn_call ... !!8 + (); %!!12 end %!!4 end%!!0 - also no space after end -function fcn_call(inp) %!!0 +function a=fcn_call(inp) %!!0 while inp > 0 %!!4 fcn_call(inp-1); %!!8 + a = [ 1 2 ... !!8 + 3 4 ]; %!!14 end %!!4 end %!!0 diff --git a/tests/mfuncnoendblock.m b/tests/mfuncnoendblock.m new file mode 100644 index 0000000..e08c568 --- /dev/null +++ b/tests/mfuncnoendblock.m @@ -0,0 +1,18 @@ +%{ + Add a block comment at the beginning to skip over. +%} +function mfuncnoendblock +% A function file that does not have ends at the end of functions. +% +% %%% function nil + +fcn_call(1) + + +function fcn_call(idx) + +if idx > 0 + fcn_call(ix-1) +end + + \ No newline at end of file From cc1a00548d79a62769aa263e4f92c65f64c61a7e Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 8 Mar 2021 16:50:16 -0500 Subject: [PATCH 029/168] matlab.el: (beginning-of-command): Optimization for very long arrays where user put ... inside the array. ie - compute beginning of array bounds first, then look for continuations. --- matlab.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/matlab.el b/matlab.el index 452a955..d5e587c 100644 --- a/matlab.el +++ b/matlab.el @@ -2111,9 +2111,10 @@ Travels across continuations." (goto-char (car bc)) ;; Scan across lines that are related. - (while (and (or (matlab-prev-line-cont) + (while (and (or (setq p (matlab-lattr-array-cont r)) ;; do this first b/c fast + (matlab-prev-line-cont) (matlab-ltype-continued-comm) - (setq p (matlab-lattr-array-cont r))) + ) (save-excursion (beginning-of-line) (not (bobp)))) (if p (goto-char p) (matlab-prev-line)) (setq p nil))) From e9b66d32ab4504824741a3a01c34a4081a76fa85 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 8 Mar 2021 18:50:01 -0500 Subject: [PATCH 030/168] matlab.el: (matlab-end-of-command): In first check, swap order looking for array-cont first since it is faster. (matlab-property-function, matlab-lattr-semantics): Deleted (matlab-show-cursor-context): Deleted. matlab-complete.el: (matlab-lattr-semantics): Moved here from matlab.el --- matlab-complete.el | 25 +++++++++++++++++++++++++ matlab.el | 44 ++------------------------------------------ 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/matlab-complete.el b/matlab-complete.el index e69af4e..a26c6ba 100644 --- a/matlab-complete.el +++ b/matlab-complete.el @@ -182,6 +182,31 @@ This list still needs lots of help.") tl (cdr tl))) r)) +(defun matlab-lattr-semantics (&optional prefix) + "Return the semantics of the current position. +Values are nil 'solo, 'value, and 'boolean. Boolean is a subset of +value. nil means there is no semantic content (ie, string or comment.) +If optional PREFIX, then return 'solo if that is the only thing on the +line." + (cond ;((matlab-cursor-in-string-or-comment) + ;nil) + ((or (matlab-ltype-empty) + (and prefix (save-excursion + (beginning-of-line) + (looking-at (concat "\\s-*" prefix "\\s-*$"))))) + 'solo) + ((save-excursion + (matlab-beginning-of-command) + (looking-at "\\s-*\\(if\\|elseif\\|while\\)\\>")) + 'boolean) + ((save-excursion + (matlab-beginning-of-command) + (looking-at (concat "\\s-*\\(" (matlab-property-function) + "\\)\\>"))) + 'property) + (t + 'value))) + ;;; Completion Framework =================================================== ;; (defun matlab-uniquify-list (lst) diff --git a/matlab.el b/matlab.el index d5e587c..f89631b 100644 --- a/matlab.el +++ b/matlab.el @@ -2127,8 +2127,8 @@ Optional BEGINNING is where the command starts from." (while (and (or (matlab-lattr-cont) (save-excursion (forward-line 1) - (or (matlab-ltype-continued-comm) - (matlab-lattr-array-cont beginning)))) + (or (matlab-lattr-array-cont beginning) + (matlab-ltype-continued-comm)))) ;; This hack is a short circuit. If a user did not ;; correctly end a matrix, this will short-circuit ;; as soon as something that would never appear in a matrix @@ -2413,32 +2413,6 @@ Argument START is where to start searching from." (and (looking-at (concat "\\<" (matlab-block-end-re) "\\>")) (matlab-valid-end-construct-p)))) -(declare-function matlab-property-function "matlab-complete" ()) -(defun matlab-lattr-semantics (&optional prefix) - "Return the semantics of the current position. -Values are nil 'solo, 'value, and 'boolean. Boolean is a subset of -value. nil means there is no semantic content (ie, string or comment.) -If optional PREFIX, then return 'solo if that is the only thing on the -line." - (cond ;((matlab-cursor-in-string-or-comment) - ;nil) - ((or (matlab-ltype-empty) - (and prefix (save-excursion - (beginning-of-line) - (looking-at (concat "\\s-*" prefix "\\s-*$"))))) - 'solo) - ((save-excursion - (matlab-beginning-of-command) - (looking-at "\\s-*\\(if\\|elseif\\|while\\)\\>")) - 'boolean) - ((save-excursion - (matlab-beginning-of-command) - (looking-at (concat "\\s-*\\(" (matlab-property-function) - "\\)\\>"))) - 'property) - (t - 'value))) - (defun matlab-function-called-at-point () "Return a string representing the function called nearby point." (save-excursion @@ -2450,20 +2424,6 @@ line." (match-string 1)) (t nil)))) -(defun matlab-show-cursor-context () - "Display something about the context the cursor is in." - (interactive) - (let* ((bounds nil) - (ctxt (matlab-cursor-comment-string-context 'bounds))) - (if (not ctxt) - (message "Cursor not in a comment or string.") - - (message "Ctxt: %s Bounds: %S" ctxt bounds) - (when (featurep 'pulse) - (pulse-momentary-highlight-region (car bounds) (car (cdr bounds))) - )))) - - (defun matlab-comment-on-line () "Place the cursor on the beginning of a valid comment on this line. From a8229c28e066b6568b1832d528af3ceb525c5761 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 8 Mar 2021 21:58:25 -0500 Subject: [PATCH 031/168] matlab.el: (matlab-beginning-of-command): Remove searching for function/block to restrict motion to. The search and restrict just slowed down ppss. (matlab-end-of-command): Remove optional input. Stop restriting array-cont query. (matlab-semicolon-on-return, matlab-mode-vf-quiesce-buffer): Don't pass input to `matlab-end-of-command'. matlab-syntax.el: (matlab-up-list): Use `matlab-beginning-of-string-or-comment' instead of asking for the string or commment bounds. The beginning is part of ppss, but full bounds includes the end. --- matlab-syntax.el | 28 ++++++++++++---------------- matlab.el | 28 +++++++++------------------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 53ddd78..f6fa95d 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -451,22 +451,18 @@ comment and string crossing. Optional argument RESTRICT specifies max point to travel to." (save-restriction (when restrict (narrow-to-region restrict (point))) - (let* ((bounds nil) - (ctxt (matlab-cursor-comment-string-context 'bounds))) - (when ctxt - (goto-char (if (< 0 count) (car bounds) (cdr bounds)))) - (if (< count 0) - (let ((pps (syntax-ppss))) - (when (or (not (numberp (nth 0 pps))) - (< (nth 0 pps) (abs count))) - (error "Cannot navigate up %d lists" (abs count))) - ;; When travelling in reverse, we can just use pps' - ;; parsed paren list in slot 9. - (let ((posn (reverse (nth 9 pps)))) ;; Location of parens - (goto-char (nth (1- (abs count)) posn)))) - ;; Else - travel forward - (up-list count nil t)) ;; will this correctly ignore comments, etc? - ))) + (matlab-beginning-of-string-or-comment) + (if (< count 0) + (let ((pps (syntax-ppss))) + (when (< (nth 0 pps) (abs count)) + (error "Cannot navigate up %d lists" (abs count))) + ;; When travelling in reverse, we can just use pps' + ;; parsed paren list in slot 9. + (let ((posn (reverse (nth 9 pps)))) ;; Location of parens + (goto-char (nth (1- (abs count)) posn)))) + ;; Else - travel forward + (up-list count nil t)) ;; will this correctly ignore comments, etc? + )) ;;; Syntax Compat functions ;; diff --git a/matlab.el b/matlab.el index f89631b..a8e5034 100644 --- a/matlab.el +++ b/matlab.el @@ -2095,23 +2095,13 @@ Travels across continuations." (beginning-of-line) (save-match-data (let ((p nil) - ;; This restriction is a wild guess where to end reverse - ;; searching for array continuations. The reason is that - ;; matlab up list is very slow, and most people would never - ;; put a blank line in a matrix. Either way, it's worth the - ;; trade off to speed this up for large files. - ;; This list of keywords is NOT meant to be comprehensive. - (r (save-excursion - (re-search-backward - "^\\s-*\\(%\\|if\\|else\\(if\\)\\|while\\|\\(par\\)?for\\|$\\)\\>" - nil t))) (bc (matlab-block-comment-bounds))) - (if bc - ;; block comment - just go to the beginning. + ;; block comment - just go to the beginning. + (if bc (goto-char (car bc)) - ;; Scan across lines that are related. - (while (and (or (setq p (matlab-lattr-array-cont r)) ;; do this first b/c fast + ;; ELSE : Scan across lines that are related. + (while (and (or (setq p (matlab-lattr-array-cont)) ;; do this first b/c fast (matlab-prev-line-cont) (matlab-ltype-continued-comm) ) @@ -2120,14 +2110,14 @@ Travels across continuations." (setq p nil))) (back-to-indentation)))) -(defun matlab-end-of-command (&optional beginning) +(defun matlab-end-of-command () "Go to the end of an M command. -Optional BEGINNING is where the command starts from." +Travells a cross continuations" (interactive) (while (and (or (matlab-lattr-cont) (save-excursion (forward-line 1) - (or (matlab-lattr-array-cont beginning) + (or (matlab-lattr-array-cont) (matlab-ltype-continued-comm)))) ;; This hack is a short circuit. If a user did not ;; correctly end a matrix, this will short-circuit @@ -2880,7 +2870,7 @@ Must be one of: (looking-at "\\s-*;"))) (save-excursion (let ((p (point))) - (matlab-end-of-command (point)) + (matlab-end-of-command) (eq p (point)))) (save-excursion (matlab-beginning-of-command) @@ -4108,7 +4098,7 @@ desired. Optional argument FAST is not used." (setq msgpos (+ msgpos dir)) (if (or (> msgpos 5) (< msgpos 0)) (setq dir (- dir) msgpos (+ (* 2 dir) msgpos))) - (matlab-end-of-command (point)) + (matlab-end-of-command) (if (matlab-cursor-in-comment) (progn (matlab-comment-on-line) From 7a3e1e872b654bda658a37fd4e6455a452bb9796 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 8 Mar 2021 22:08:38 -0500 Subject: [PATCH 032/168] matlab-syntax.el: (matlab-up-list): Remove restrict option matlab.el: (matlab-lattr-array-cont): Remove restrict option. --- matlab-syntax.el | 6 ++---- matlab.el | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index f6fa95d..e0c35b4 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -442,15 +442,13 @@ of character based." ;; We used to need to do special stuff, but now I think this ;; is just a call striaght to up-list. -(defun matlab-up-list (count &optional restrict) +(defun matlab-up-list (count) "Move forwards or backwards up a list by COUNT. When travelling backward, use `syntax-ppss' counted paren starts to navigate upward. When travelling forward, use 'up-list' diretly, but disable -comment and string crossing. -Optional argument RESTRICT specifies max point to travel to." +comment and string crossing." (save-restriction - (when restrict (narrow-to-region restrict (point))) (matlab-beginning-of-string-or-comment) (if (< count 0) (let ((pps (syntax-ppss))) diff --git a/matlab.el b/matlab.el index a8e5034..ff351a5 100644 --- a/matlab.el +++ b/matlab.el @@ -2277,14 +2277,13 @@ based on what it ends with." (save-excursion (and (if (= -1 (forward-line -1)) nil t) (matlab-lattr-cont)))) -(defun matlab-lattr-array-cont (&optional restrict) +(defun matlab-lattr-array-cont () "Return non-nil if current line is in an array. -If the entirety of the array is on this line, return nil. -Optional option RESTRICT is the distance to restrict the search." +If the entirety of the array is on this line, return nil." (condition-case nil (save-excursion (beginning-of-line) - (matlab-up-list -1 restrict) + (matlab-up-list -1) (and (looking-at "[[{]") (point))) (error nil))) From 7f9c9052381cc18c082532424c75ef962d2aae83 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 8 Mar 2021 22:34:25 -0500 Subject: [PATCH 033/168] matlab.el: (matlab-lattr-cont): Replace old regexp based searching for ... with use of syntax-ppss, and using it to find the char that starts the comment, and use to verify an ellipsis. --- matlab.el | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/matlab.el b/matlab.el index ff351a5..6e357d0 100644 --- a/matlab.el +++ b/matlab.el @@ -2258,14 +2258,13 @@ This is only useful for new versions of MATLAB where ... is optional." If `matlab-cont-requires-ellipsis' is nil, then we need to apply a heuristic to determine if this line would use continuation based on what it ends with." - (save-excursion - (beginning-of-line) + (let* ((pps (syntax-ppss (point-at-eol))) + (csc (nth 8 pps))) (or - ;; Here, if the line ends in ..., then it is what we are supposed to do. - (and (re-search-forward "[^ \t.][ \t]*\\(\\.\\.\\.+\\)[ \t]*\\(.*\\)?$" - (matlab-point-at-eol) t) - (progn (goto-char (match-beginning 1)) - (not (matlab-cursor-in-string-or-comment)))) + ;; When the line ends with a comment, it might be an ellipsis. + ;; Can only be tail comment w/ no % start if it is an ellipsis. + (and csc (/= (char-after csc) ?\%)) + ;; If the line doesn't end in ..., but we have optional ..., then ;; use this annoying heuristic. (and (null matlab-cont-requires-ellipsis) From 81f2cc0fd76b45bfb650237517894d1d3336cb71 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 9 Mar 2021 12:06:03 -0500 Subject: [PATCH 034/168] matlab.el: (matlab-show-paren-or-block): Use `syntax-class' instead of `car' for output of `syntax-after' to get the class. This strips out high order bits to return the actual syntax #. --- matlab.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/matlab.el b/matlab.el index 6e357d0..a103f49 100644 --- a/matlab.el +++ b/matlab.el @@ -3503,7 +3503,7 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" ;; These checks are much faster than regexp ;; Step one - check for parens - (cond ((and here-syntax (= (car here-syntax) 4)) ; open paren + (cond ((and here-syntax (= (syntax-class here-syntax) 4)) ; open paren (setq here-beg (point) here-end (1+ (point))) (condition-case err @@ -3513,14 +3513,14 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" there-end (point) there-syntax (syntax-after there-beg) there-char (char-after there-beg)) - (when (or (/= (car there-syntax) 5) + (when (or (/= (syntax-class there-syntax) 5) (/= (cdr there-syntax) here-char) (/= (cdr here-syntax) there-char)) ; this part seems optional ;(message "ts = %S hs=%S tc = %d hc = %d" there-syntax here-syntax there-char here-char) (setq mismatch t)) ) (error (setq mismatch t)))) - ((and here-prev-syntax (= (car here-prev-syntax) 5)) + ((and here-prev-syntax (= (syntax-class here-prev-syntax) 5)) (setq here-beg (1- (point)) here-end (point)) (condition-case err @@ -3530,7 +3530,7 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" there-beg (point) there-syntax (syntax-after there-beg) there-char (char-after there-beg)) - (when (or (/= (car there-syntax) 4) + (when (or (/= (syntax-class there-syntax) 4) (/= (cdr there-syntax) here-prev-char) (/= (cdr here-prev-syntax) there-char)) ; this part seems optional (setq mismatch t)) @@ -3543,7 +3543,7 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" ;; word character move back one symbol. This will let ;; us use the block begin / end matchers to figure ;; out where we are. - (when (and (not (eobp)) (not (bobp)) (= (car here-prev-syntax) 2)) + (when (and (not (eobp)) (not (bobp)) (= (syntax-class here-prev-syntax) 2)) (forward-symbol -1)) (matlab-navigation-syntax From 7f6f729fd3e79965aba4e0ecf417df9e7b8eeb42 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 9 Mar 2021 12:50:59 -0500 Subject: [PATCH 035/168] matlab.el: (matlab-calc-indent): If current line detects we are in an array continuation, then it is not necessary to compute next-line indentation from it's context since you can't have if/end type code in there. Also, array continuation detection is now very fast. tests/indents.m: Added a bunch of misc indenttion tests around nested arrays, cell arrays, etc. --- matlab.el | 13 +++++++++++-- tests/indents.m | 20 +++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/matlab.el b/matlab.el index a103f49..60f5f55 100644 --- a/matlab.el +++ b/matlab.el @@ -2452,9 +2452,18 @@ If there isn't one, then return nil, point otherwise." (interactive) ;; The first step is to find the current indentation. ;; This is defined to be zero if all previous lines are empty. - (let* ((ci (save-excursion (if (not (matlab-prev-line)) + (let* ((ci (if (matlab-lattr-array-cont) + ;; If we are inside an array continuation, then we shouldn't + ;; need to do anything complicated here b/c we'll just ignore + ;; the returned value in the next step. Return current indentation. + (save-excursion + (matlab-prev-line) + (current-indentation)) + ;; Else, the next line might recommend an indentation based + ;; on it's own context. + (save-excursion (if (not (matlab-prev-line)) 0 - (matlab-next-line-indentation)))) + (matlab-next-line-indentation))))) (sem (matlab-calculate-indentation ci))) ;; simplistic (nth 1 sem))) diff --git a/tests/indents.m b/tests/indents.m index 7290381..5177f61 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -52,8 +52,9 @@ function B = ends_in_comments_and_strings() symbol_with_end_in_it; B = A(1:end); %#ok - - if foo + + %% cell start comment !!4 + if foo %!!4 C = "this is the end of the line"; % !!8 else %!!4 @@ -89,7 +90,7 @@ function B = ends_in_comments_and_strings() end ... the other end % !! 4 - code1() ... + code1(), ... code2(); %!!8 % NOTE: Blank space below should cancel the indent effect of ellipsis. @@ -133,10 +134,23 @@ function out = array_constant_decls() 3 4; %!!8 ]; %!!11 + multinest = { [ 1 2 %!!4 + 3 4 ]; %!!20 + { 5 6 7 ... %!!18 + 8 9 10 }; %!!20 + fcncall(10, ... %!!18 + 12, ... %!!26 + [ 13 14; %!!26 + 15 16 ]) %!!28 + } ; %!!16 + + % !!4 out = { A %!!4 Blong %!!12 Csep %!!12 + multinest%!!12 }; %!!10 + end function C = block_starts_in_comments_and_strings() From d96cb11f2fce3a347a4f463ffdb90ca11b4c45d1 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 9 Mar 2021 19:21:03 -0500 Subject: [PATCH 036/168] matlab.el: (matlab-beginning-of-command): Add hack to skip over multiple continued comments very fast. This is a very specific optimization for many lines of comments. --- matlab.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index 60f5f55..9c41d8a 100644 --- a/matlab.el +++ b/matlab.el @@ -2101,9 +2101,18 @@ Travels across continuations." (goto-char (car bc)) ;; ELSE : Scan across lines that are related. + ;; Step one, skip all comments indented as continutions of a previous. + ;; Using forward-comment is very fast, and just skipps all comments until + ;; we hit a line of code. + ;; NOTE: This may fail with poorly indented code. + (when (matlab-ltype-continued-comm) + (forward-comment -100000)) + + ;; Now walk backward across continued code lines. (while (and (or (setq p (matlab-lattr-array-cont)) ;; do this first b/c fast (matlab-prev-line-cont) - (matlab-ltype-continued-comm) + ;; We used to do this, now handled w/ forward-comment above. + ;;(matlab-ltype-continued-comm) ) (save-excursion (beginning-of-line) (not (bobp)))) (if p (goto-char p) (matlab-prev-line)) From 27e654c9c0765cb43858ac4b2606b0acd14c63fc Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 19:50:50 -0500 Subject: [PATCH 037/168] tests/metest.el: (metest-all-syntax-tests): Add timing instrumentation. (metest-condition-case-error-msg): New utility. (metest-indents-test): Wrap indent computation in `metest-condition-case-error-msg'. (metest-log-file): New cofig (metest-log-init, metest-shorten, metest-log-write) (metest-log-report, metest-timeint): New. --- tests/metest.el | 108 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/tests/metest.el b/tests/metest.el index b592972..bccaf70 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -42,14 +42,28 @@ (defun metest-all-syntax-tests () "Run all the syntax tests in this file." - (metest-end-detect-test) - (metest-comment-string-syntax-test) - (metest-sexp-counting-test) - (metest-sexp-traversal-test) - (metest-indents-test) - (metest-parse-test) + (metest-log-init) + + (metest-timeit 'metest-end-detect-test) + (metest-timeit 'metest-comment-string-syntax-test) + (metest-timeit 'metest-sexp-counting-test) + (metest-timeit 'metest-sexp-traversal-test) + (metest-timeit 'metest-indents-test) + (metest-timeit 'metest-parse-test) + + (setq debug-on-error t) + (metest-log-report (metest-log-write)) ) +(defmacro metest-condition-case-error-msg (&rest forms) + "Run FORMS, capturing any errors and associating with (point)." + (declare (indent 0) (debug t)) + `(condition-case err + ,@forms + (error (metest-error "Lisp: %s" (error-message-string err)) + 0) + )) + (defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" ) "List of files for running end detection tests on.") @@ -215,17 +229,18 @@ (message ">> Starting indents loop in %S" (current-buffer)) (while (re-search-forward "!!\\([0-9]+\\)" nil t) (let* ((num (string-to-number (match-string 1))) - (calc (matlab-calc-indent)) + (calc (metest-condition-case-error-msg + (matlab-calc-indent))) (begin nil)) - (when (not (= num calc)) - (metest-error "Indentation found is %d, expected %d" - calc num)) + (when (not (eq num calc)) + (metest-error "Indentation found is %s, expected %s" + calc num)) ) (end-of-line) (setq cnt (1+ cnt)))) - (kill-buffer buf) - (message "<< Indentation syntax test: %d points passed" cnt) - )) + (kill-buffer buf) + (message "<< Indentation syntax test: %s points passed" cnt) + )) (message "")) (defvar met-parser-files '("mpclass.m") @@ -277,7 +292,6 @@ (defun metest-compare-tags (EXP ACT) "Return non-nil if EXP tag is similiar to ACT" (semantic-tag-similar-p EXP ACT :documentation) - ) (defun metest-find-file (file) @@ -295,7 +309,71 @@ Do error checking to provide easier debugging." (file-name-nondirectory (buffer-file-name)) (line-number-at-pos))) (post (apply 'format args))) - (error (concat pre post)))) + (message (concat pre post)))) + +;;; Logging prormance data for the tests +;; +(defvar metest-log-file "metest_timing_log.dat" + "File to store timing data to.") + +(defvar metest-time-log nil + "Data stored for each run.") + +(defun metest-log-init () + "Init the log file and data variable." + (setq metest-time-log nil) + ) + +(defun metest-shorten (sym) + "Convert SYM into a column header." + (let ((str (symbol-name sym))) + (substring str 7 -5))) + +(defun metest-log-write () + "Write dta into our log file." + (save-current-buffer + (set-buffer (find-file-noselect metest-log-file)) + (let ((LOG (reverse metest-time-log))) + (when (= (point-min) (point-max)) + ;; Initialize the new buffer + (insert "Time\t") + (insert (mapconcat (lambda (log) (metest-shorten (car log))) LOG "\t"))) + ;; Insert our measurements + (goto-char (point-max)) + (newline) + (insert (format-time-string "\"%Y/%m/%d %H:%M\"\t" (current-time))) + (insert (mapconcat (lambda (log2) (format "%f" (cdr log2))) LOG "\t")) + (save-buffer) + ;; Go back and find our baseline and return it. + (goto-char (point-min)) + (forward-line 1) + (read (concat "(" (buffer-substring-no-properties (point-at-bol) (point-at-eol)) ")")) + ))) + +(defun metest-log-report (baseline) + "Report via message what happened during the test suite." + (let ((log (reverse metest-time-log)) + (base (cdr baseline))) + (princ "Baseln\tRun\tImprovement\tTest\n") + (while (and log base) + (princ (format "%.4f\t" (car base))) + (princ (format "%.4f\t" (cdr (car log)))) + (princ (format "%.4f\t\t" (- (car base) (cdr (car log))))) + (princ (metest-shorten (car (car log)))) + (princ "\n") + (setq log (cdr log) + base (cdr base))) + )) + +(defun metest-timeit (fcn) + "Time running FCN and save result in LOGFILE. +Use this to track perforamnce improvements during development automatically." + (let* ((start (current-time)) + (out (funcall fcn)) + (end (current-time))) + (push (cons fcn (float-time (time-subtract end start))) + metest-time-log) + )) (provide 'metest) From e57ff3e005178e5ed96c96ee5256be6788bfef8d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 19:54:04 -0500 Subject: [PATCH 038/168] metest.el: (metest-error): Convert back to error. --- tests/metest.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/metest.el b/tests/metest.el index bccaf70..4d16604 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -309,7 +309,7 @@ Do error checking to provide easier debugging." (file-name-nondirectory (buffer-file-name)) (line-number-at-pos))) (post (apply 'format args))) - (message (concat pre post)))) + (error (concat pre post)))) ;;; Logging prormance data for the tests ;; From 174f749e1ffc85a7118e1e9289b31bbbc44c2447 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 19:57:23 -0500 Subject: [PATCH 039/168] matlab-scan.el: New algorithms for scanning MATLAB files for context. matlab.el: (matlab-mode-leave): Disable scanner in this buffer. (matlab-mode): Enable scanner in this buffer. (matlab-calculate-indentation-1): Use `matlab-compute-line-context'. Use output for comment detection. Move block comment stuff first to maintain consistency with old behavior. Project.ede, Makefile: Add matlab-scan.el --- Makefile | 2 +- Project.ede | 2 +- matlab.el | 147 ++++++++++++++++++++++++++-------------------------- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index a743bc0..c235167 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ LOADPATH= ./ LOADDEFS=matlab-load.el LOADDIRS=. misc_MISC=ChangeLog ChangeLog.old1 ChangeLog.old2 INSTALL README dl_emacs_support.m -lisp_LISP=matlab-compat.el matlab-syntax.el matlab.el matlab-shell.el matlab-shell-gud.el matlab-netshell.el matlab-complete.el matlab-cgen.el matlab-publish.el matlab-topic.el mlint.el tlc.el linemark.el matlab-maint.el +lisp_LISP=matlab-compat.el matlab-syntax.el matlab-scan.el matlab.el matlab-shell.el matlab-shell-gud.el matlab-netshell.el matlab-complete.el matlab-cgen.el matlab-publish.el matlab-topic.el mlint.el tlc.el linemark.el matlab-maint.el cedet_LISP=semantic-matlab.el semanticdb-matlab.el srecode-matlab.el cedet-matlab.el company-matlab-shell.el VERSION=4.0 DISTDIR=$(top)matlab-emacs-$(VERSION) diff --git a/Project.ede b/Project.ede index a006c7b..3a7541c 100644 --- a/Project.ede +++ b/Project.ede @@ -17,7 +17,7 @@ (ede-proj-target-elisp "ede-proj-target-elisp" :name "lisp" :path "" - :source '("matlab-compat.el" "matlab-syntax.el" "matlab.el" "matlab-shell.el" "matlab-shell-gud.el" "matlab-netshell.el" "matlab-complete.el" "matlab-cgen.el" "matlab-publish.el" "matlab-topic.el" "mlint.el" "tlc.el" "linemark.el" "matlab-maint.el") + :source '("matlab-compat.el" "matlab-syntax.el" "matlab-scan.el" "matlab.el" "matlab-shell.el" "matlab-shell-gud.el" "matlab-netshell.el" "matlab-complete.el" "matlab-cgen.el" "matlab-publish.el" "matlab-topic.el" "mlint.el" "tlc.el" "linemark.el" "matlab-maint.el") :versionsource '("matlab.el") :aux-packages '("matlab")) (ede-proj-target-elisp "ede-proj-target-elisp" diff --git a/matlab.el b/matlab.el index 9c41d8a..6dfebd8 100644 --- a/matlab.el +++ b/matlab.el @@ -52,6 +52,7 @@ (require 'matlab-compat) (require 'matlab-syntax) +(require 'matlab-scan) (require 'easymenu) (require 'derived) @@ -1257,7 +1258,9 @@ indicates as such." (defun matlab-mode-leave () "When leaving `matlab-mode', turn off `mlint-minor-mode'" (when (eq major-mode 'matlab-mode) - (mlint-minor-mode -1))) + (mlint-minor-mode -1) + (matlab-scan-disable) + )) ;;;###autoload (define-derived-mode matlab-mode prog-mode "MATLAB" @@ -1328,7 +1331,8 @@ All Key Bindings: ;; This includes syntax table definitions, misc syntax regexps ;; and font-lock for comments/strings. (matlab-syntax-setup) - + (matlab-scan-setup) + ;; Indentation setup. (setq indent-tabs-mode nil) (make-local-variable 'indent-line-function) @@ -2505,44 +2509,41 @@ otherwise return NONINTERACTIVE-DEFAULT" "Do the indentation work of `matlab-calculate-indentation'. Argument CURRENT-INDENTATION is what the previous line recommends for indentation." (let ((ci current-indentation) + (lvl1 (matlab-compute-line-context 1)) (blockcomm nil) (tmp nil)) (cond + ;; BLOCK COMMENT END _or_ body prefixed with % + ((and (setq blockcomm (save-excursion + (back-to-indentation) + (matlab-block-comment-bounds))) + (or (matlab-ltype-block-comment-end) + (matlab-ltype-comm-noblock))) + (list 'comment (save-excursion + (goto-char (car blockcomm)) + (current-indentation))) + ) + ;; BLOCK COMMENT START + ((matlab-ltype-block-comment-start) + (list 'comment (+ ci matlab-comment-anti-indent)) + ) + ;; BLOCK COMMENT BODY. + (blockcomm + (list 'comment + (+ (save-excursion + (goto-char (car blockcomm)) + (current-indentation)) + 2)) + ) ;; COMMENTS - ((matlab-ltype-comm) + ((matlab-line-comment-p lvl1) (cond ;; HELP COMMENT and COMMENT REGION - ((or (setq tmp (matlab-ltype-help-comm)) - (matlab-ltype-comm-ignore)) - (list 'comment-help - (if tmp - (save-excursion - (goto-char tmp) - (current-indentation)) - (save-excursion - (matlab-beginning-of-defun) - (current-indentation))))) - ;; BLOCK COMMENT END _or_ body prefixed with % - ((and (setq blockcomm (matlab-block-comment-bounds)) - (or (matlab-ltype-block-comment-end) - (matlab-ltype-comm-noblock))) - (list 'comment (save-excursion - (goto-char (car blockcomm)) - (current-indentation))) - ) - ;; BLOCK COMMENT START - ((and blockcomm - (matlab-ltype-block-comment-start)) - (list 'comment (+ ci matlab-comment-anti-indent)) - ) - ;; BLOCK COMMENT BODY. - (blockcomm - (list 'comment - (+ (save-excursion - (goto-char (car blockcomm)) - (current-indentation)) - 2)) - ) + ((setq tmp (matlab-line-comment-help-p lvl1)) + (list 'comment-help tmp)) + ;; COMMENT REGION comments + ((matlab-line-comment-ignore-p lvl1) + (list 'comment-ignore 0)) ;; COMMENT Continued From Previous Line ((setq tmp (matlab-ltype-continued-comm)) (list 'comment tmp)) @@ -2599,7 +2600,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (goto-char (match-beginning 0)) (current-column))) ) - (setq ci (- match-col-end match-col-beginning))) + (setq ci (- match-col-end match-col-beginning))) (throw 'indent nil))))) ;; End of special case for end and match after "^[ \t]*". (setq ci (+ ci @@ -2633,20 +2634,20 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio ;; End of a MATRIX ((matlab-lattr-array-end) (list 'array-end (save-excursion - (back-to-indentation) - (matlab-up-list -1) - (let* ((fc (following-char)) - (mi (assoc fc matlab-maximum-indents)) - (max (if mi (if (listp (cdr mi)) - (car (cdr mi)) (cdr mi)) - nil)) - (ind (if mi (if (listp (cdr mi)) - (cdr (cdr mi)) (cdr mi)) - nil))) - ;; apply the maximum limits. - (if (and ind (> (- (current-column) ci) max)) - (1- ind) ; decor - (current-column)))))) + (back-to-indentation) + (matlab-up-list -1) + (let* ((fc (following-char)) + (mi (assoc fc matlab-maximum-indents)) + (max (if mi (if (listp (cdr mi)) + (car (cdr mi)) (cdr mi)) + nil)) + (ind (if mi (if (listp (cdr mi)) + (cdr (cdr mi)) (cdr mi)) + nil))) + ;; apply the maximum limits. + (if (and ind (> (- (current-column) ci) max)) + (1- ind) ; decor + (current-column)))))) ;; Code lines ((and (not (matlab-lattr-array-cont)) (not (matlab-prev-line-cont))) @@ -2741,30 +2742,30 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (if (and ind (> (- (current-column) ci) max)) (+ ci ind) (current-column)))))))) - (error - ;; Line up to an equals sign. - (save-excursion - (goto-char boc) - (while (and (re-search-forward "=" (matlab-point-at-eol) t) - (matlab-cursor-in-string-or-comment))) - (if (/= (preceding-char) ?=) - (+ ci matlab-cont-level) - (skip-chars-forward " \t") - (let ((cc (current-column)) - (mi (assoc ?= matlab-maximum-indents))) - (if (looking-at "\\.\\.\\.\\|$") - ;; In this case, the user obviously wants the - ;; indentation to be somewhere else. - (+ ci (cdr (cdr mi))) - ;; If the indent delta is greater than the max, - ;; use the max + current - (if (and mi (> (- cc ci) (if (listp (cdr mi)) - (car (cdr mi)) - (cdr mi)))) - (setq cc (+ ci (if (listp (cdr mi)) - (cdr (cdr mi)) - (cdr mi))))) - cc))))))))) + (error + ;; Line up to an equals sign. + (save-excursion + (goto-char boc) + (while (and (re-search-forward "=" (matlab-point-at-eol) t) + (matlab-cursor-in-string-or-comment))) + (if (/= (preceding-char) ?=) + (+ ci matlab-cont-level) + (skip-chars-forward " \t") + (let ((cc (current-column)) + (mi (assoc ?= matlab-maximum-indents))) + (if (looking-at "\\.\\.\\.\\|$") + ;; In this case, the user obviously wants the + ;; indentation to be somewhere else. + (+ ci (cdr (cdr mi))) + ;; If the indent delta is greater than the max, + ;; use the max + current + (if (and mi (> (- cc ci) (if (listp (cdr mi)) + (car (cdr mi)) + (cdr mi)))) + (setq cc (+ ci (if (listp (cdr mi)) + (cdr (cdr mi)) + (cdr mi))))) + cc))))))))) ))) (defun matlab-next-line-indentation () From a948c123fe9a4f5a1e1e55b989b9b05e71870935 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 19:57:46 -0500 Subject: [PATCH 040/168] matlab-scan.el: New algorithms for scanning MATLAB files for context. --- matlab-scan.el | 341 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 matlab-scan.el diff --git a/matlab-scan.el b/matlab-scan.el new file mode 100644 index 0000000..3fc3822 --- /dev/null +++ b/matlab-scan.el @@ -0,0 +1,341 @@ +;;; matlab-scan.el --- Tools for contextually scanning a MATLAB buffer +;; +;; Copyright (C) 2021 Eric Ludlam +;; +;; Author: +;; +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see https://www.gnu.org/licenses/. + +;;; Commentary: +;; +;; Handle all the scanning and computing for MATLAB files. +;; +;; * Regular expressions for finding different kinds of syntax +;; * Systems for detecting what is on a line +;; * Systems for computing indentation + +(require 'matlab-syntax) + +;;; Code: + +;;; Keyword and REGEX constants +;; +(defconst matlab-block-keyword-list '(("end" . end) + ("function" . decl) + ("classdef" . decl) + ("arguments" . args) + ("properties" . mcos) + ("methods" . mcos) + ("events" . mcos) + ("enumeration" . mcos) + ("if" . ctrl) + ("else" . mid) + ("ifelse" . mid) + ("for" . ctrl) + ("parfor" . ctrl) + ("while" . ctrl) + ("switch" . ctrl) + ("case" . case) + ("otherwise" . case) + ("try" . ctrl) + ("catch" . mid) + ) + "List of keywords that are part of code blocks.") + +(defconst matlab-keyword-table + (let ((ans (obarray-make 23))) + (mapc (lambda (elt) (set (intern (car elt) ans) (cdr elt))) + matlab-block-keyword-list) + ans) + "Keyword table for fast lookups of different keywords and their purpose.") + +;;; Context Parsing +;; +;; Find some fast ways to identify the context for a given line. +;; Use tricks to derive multiple pieces of information and do lookups +;; as quickly as possible. + + +(defun matlab-compute-line-context (level) + "Compute and return the line context for the current line of MATLAB code. +LEVEL indicates how much information to return. +LEVEL of 1 is the most primitive / simplest data. +This function caches compuated context onto the line it is for, and will +return the cache if it finds it." + (cond ((= level 1) + (let ((ctxt (matlab-scan-cache-get 1))) + (unless ctxt + (setq ctxt (matlab-compute-line-context-lvl-1)) + (matlab-scan-cache-put ctxt 1)) + ctxt)) + (t + nil)) + ) + +(defconst mlf-ltype 0) +(defconst mlf-stype 1) +(defconst mlf-indent 2) +(defconst mlf-paren-depth 3) +(defconst mlf-paren-char 4) +(defconst mlf-paren-col 5) +(defconst mlf-paren-delta 6) +(defconst mlf-end-comment-type 7) +(defconst mlf-end-comment-col 8) + +(defun matlab-compute-line-context-lvl-1 () + "Compute and return the level1 context for the current line of MATLAB code. +Level 1 contexts are things quickly derived from `syntax-ppss' +and other simple states. +Computes multiple styles of line by checking for multiple types of context +in a single call using fastest methods. +Return list has these fields: + 0 - Primary line type + 1 - Secondary line type + 2 - Indentation + 3 - Parenthisis depth + 4 - Char for innermost beginning paren + 5 - Column of innermost beginning paren + 6 - Parenthisis depth change on this line + 7 - End Comment type (ellipsis, comment, or nil) + 8 - End Comment start column +" + (save-excursion + (back-to-indentation) + (let* ((ppsend (save-excursion (syntax-ppss (point-at-eol)))) + (pps (syntax-ppss (point))) + (ltype 'empty) + (stype nil) + (cc 0) + (paren-depth (nth 0 pps)) + (paren-char nil) + (paren-col nil) + (paren-delta (- (car pps) (car ppsend))) + (ec-type nil) + (ec-col nil) + + ) + + ;; This means we are somewhere inside a cell, array, or arg list. + ;; Find out the kind of list we are in. + ;; Being in a multi-line list is valid for all other states like + ;; empty lines, and block comments + (when (> (nth 0 pps) 0) + (save-excursion + (goto-char (car (last (nth 9 pps)))) + (setq paren-char (char-after (point)) + paren-col (current-column)))) + + (cond + ;; For comments - We can only ever be inside a block comment, so + ;; check for that. + ;; 4 is comment flag. 7 is '2' if block comment + ((and (nth 4 pps) (eq (nth 7 pps) 2)) + (setq ltype 'comment + stype (cond ((looking-at "%}\\s-*$") + 'block-comment-end) + ((looking-at "%") + 'block-comment-body-prefix) + (t 'block-comment-body)))) + + ;; If indentation lands on end of line, this is an empty line + ;; so nothing left to do. Keep after block-comment-body check + ;; since empty lines in a block comment are valid. + ((eolp) + nil) + + ;; Looking at a % means one of the various comment flavors. + ((eq (char-after (point)) ?\%) + (setq ltype 'comment + stype (cond ((looking-at "%{\\s-*$") + 'block-comment-start) + ((looking-at "%%") + 'cell-start) + ((looking-at "% \\$\\$\\$") + 'indent-ignore) + (t nil)))) + + ;; Looking at word constituent. If so, identify if it is one of our + ;; special identifiers. + ((looking-at "\\w+\\>") + (let* ((word (match-string-no-properties 0)) + (sym (intern-soft word matlab-keyword-table)) + ) + (if sym + (if (eq (symbol-value sym) 'end) + ;; Special end keyword is in a class all it's own + (setq ltype 'end) + ;; If we found this in our keyword table, then it is a start + ;; of a block with a subtype. + (setq ltype 'block-start + stype (symbol-value sym))) + ;; Else - not a sym - just some random code. + (setq ltype 'code) + ))) + + ;; Last stand - drop in 'code' to say - yea, just some code. + (t (setq ltype 'code)) + + ) + + ;; NEXT - Check context at the end of this line, and determine special + ;; stuff about it. + + ;; When the line ends with a comment. + ;; Also tells us about continuations and comment start for lining up tail comments. + (let ((csc (nth 8 ppsend))) + (when csc + (setq ec-col csc + ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis)) ;; type + )) + + (list ltype stype cc paren-depth paren-char paren-col paren-delta ec-type ec-col) + ))) + + +(defun matlab-compute-line-context-lvl-2 (lvl1) + "Compute level 2 line contexts for indentation. +These are more expensive checks queued off of a lvl1 context." + + ;; matlab-ltype-help-comm + + ;; block start + ;; block end + ;; change in # blocks on this line. + + ) + +;;; Accessor Utilities +;; +;; Use these to query a context for a piece of data +(defsubst matlab-line-empty-p (lvl1) + "Return t if the current line is empty based on LVL1 cache." + (eq (car lvl1) 'empty)) + +;; Comments +(defsubst matlab-line-comment-p (lvl1) + "Return t if the current line is a comment based on LVL1 cache." + (eq (car lvl1) 'comment)) + +(defsubst matlab-line-comment-ignore-p (lvl1) + "Return t if the current line is a comment based on LVL1 cache." + (and (matlab-line-comment-p lvl1) (eq (nth mlf-stype lvl1) 'indent-ignore))) + +(defsubst matlab-line-block-start-keyword-p (lvl1) + "Return t if the current line starts with block keyword." + (eq (car lvl1) 'block-start)) + +;; Code and Declarations +(defsubst matlab-line-declaration-p (lvl1) + "If the current line is a declaration, return the column it starts on. +Declarations are things like function or classdef." + (and (matlab-line-block-start-keyword-p lvl1) (eq (nth mlf-stype lvl1) 'decl))) + +;;; Scanning Accessor utilities +;; +;; some utilities require some level of buffer scanning to get the answer. +;; Keep those separate so they can depend on the earlier decls. +(defun matlab-line-comment-help-p (lvl1) + "Return declaration column if the current line is part of a help comment. +Declarations are things like functions and classdefs. +Indentation a help comment depends on the column of the declaration." + (and (matlab-line-comment-p lvl1) + (save-excursion + (beginning-of-line) + (forward-comment -100000) + (let ((c-lvl1 (matlab-compute-line-context 1))) + (when (matlab-line-declaration-p c-lvl1) + (current-indentation))) + ))) + + +;;; Caching +;; +(defvar matlab-scan-temporal-cache nil + "Cache of recently computed line contexts. +Used to speed up repeated queries on the same set of lines.") +(make-variable-buffer-local 'matlab-scan-temporal-cache) + +(defun matlab-scan-cache-get (level) + "Get a cached context at level." + ;; TODO + nil) + +(defun matlab-scan-cache-put (ctxt level) + "Get a cached context at level." + ;; TODO + nil) + + +(defun matlab-scan-after-change-fcn (start end length) + "Function run in after change hooks." + (setq matlab-scan-temporal-cache nil)) + +(defun matlab-scan-setup () + "Setup use of the indent cache for the current buffer." + (interactive) + (add-hook 'after-change-functions 'matlab-scan-after-change-fcn t) + ) + +(defun matlab-scan-disable () + "Setup use of the indent cache for the current buffer." + (interactive) + (remove-hook 'after-change-functions 'matlab-scan-after-change-fcn t) + ) + +;;; Debugging and Querying +;; +(defun matlab-describe-line-indent-context () + "Describe the indentation context for the current line." + (interactive) + (let* ((lvl1 (matlab-compute-line-context 1)) + (paren-char (nth mlf-paren-char lvl1)) + (open (format "%c" (or paren-char ?\())) + (close (format "%c" + (cond ((not paren-char) ?\)) + ((= paren-char ?\() ?\)) + ((= paren-char ?\[) ?\]) + ((= paren-char ?\{) ?\}) + (t ??)))) + (extraopen "") + (extraclose "") + ) + (when (= (nth mlf-paren-depth lvl1) 0) + (setq open (propertize open 'face 'shadow) + close (propertize close 'face 'shadow))) + (if (< (nth mlf-paren-delta lvl1) 0) + (setq extraopen (format "<%d" (abs (nth mlf-paren-delta lvl1)))) + (when (> (nth mlf-paren-delta lvl1) 0) + (setq extraclose (format "%d>" (nth mlf-paren-delta lvl1))))) + + + (message "%s%s %s%s%d%s%s %s" (nth mlf-ltype lvl1) + (format " %s" (or (nth mlf-stype lvl1) "")) + ;; paren system + extraopen + open + (nth mlf-paren-depth lvl1) + close + extraclose + (cond ((eq (nth mlf-end-comment-type lvl1) 'comment) "%") + ((eq (nth mlf-end-comment-type lvl1) 'ellipsis) "...") + (t "")) + ) + + ) + ) + + +(provide 'matlab-scan) + +;;; matlab-indent.el ends here From d0cda9be1ddcab051d88d3f06e75a8c88f748b6f Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 21:33:17 -0500 Subject: [PATCH 041/168] .gitignore: Add .dat files (as generated by test build now.) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ed17e4e..e2e07ac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ .\#* *.orig *.rej +*.dat # matlab-load.el is generated matlab-load.el +parse \ No newline at end of file From 0fb21cfebf4ef6212a694d575a555b2f8ff3a741 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 21:37:51 -0500 Subject: [PATCH 042/168] matlab.el: (matlab-calculate-indentation-1): Replace old -ltype- API calls with new API from matlab-scan.el. Delete chunks of block comment handlers, replaced with simpler code using matlab-scan stuff. matlab-scan.el: (matlab-compute-line-context-lvl-1): Rename block comment stype values. When a keyword is found, only enable iff not in parens. Trailing comment section, if line starts with a comment, assume it ends with a comment, and compute key values. (matlab-line-comment-style, matlab-line-end-comment-column) (matlab-line-ellipsis-p): New (matlab-line-end-p, matlab-line-block-middle-p) (matlab-line-block-case-p): New (matlab-line-close-paren-p, matlab-line-close-paren-char) (matlab-line-close-paren-col): New (matlab-describe-line-indent-context): Fixup for various changes. --- matlab-scan.el | 105 ++++++++++++++++++++++++++++++++++++++---------- matlab.el | 105 ++++++++++++++++++++++-------------------------- tests/indents.m | 18 +++++++-- 3 files changed, 145 insertions(+), 83 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 3fc3822..060d0c9 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -143,10 +143,10 @@ Return list has these fields: ((and (nth 4 pps) (eq (nth 7 pps) 2)) (setq ltype 'comment stype (cond ((looking-at "%}\\s-*$") - 'block-comment-end) + 'block-end) ((looking-at "%") - 'block-comment-body-prefix) - (t 'block-comment-body)))) + 'block-body-prefix) + (t 'block-body)))) ;; If indentation lands on end of line, this is an empty line ;; so nothing left to do. Keep after block-comment-body check @@ -158,7 +158,7 @@ Return list has these fields: ((eq (char-after (point)) ?\%) (setq ltype 'comment stype (cond ((looking-at "%{\\s-*$") - 'block-comment-start) + 'block-start) ((looking-at "%%") 'cell-start) ((looking-at "% \\$\\$\\$") @@ -168,20 +168,29 @@ Return list has these fields: ;; Looking at word constituent. If so, identify if it is one of our ;; special identifiers. ((looking-at "\\w+\\>") - (let* ((word (match-string-no-properties 0)) - (sym (intern-soft word matlab-keyword-table)) - ) - (if sym - (if (eq (symbol-value sym) 'end) - ;; Special end keyword is in a class all it's own - (setq ltype 'end) - ;; If we found this in our keyword table, then it is a start - ;; of a block with a subtype. - (setq ltype 'block-start - stype (symbol-value sym))) - ;; Else - not a sym - just some random code. + (if (/= paren-depth 0) (setq ltype 'code) - ))) + + ;; If not in parens, this might be a keyword. + ;; Look up our various keywords. + (let* ((word (match-string-no-properties 0)) + (sym (intern-soft word matlab-keyword-table)) + ) + (if sym + (if (eq (symbol-value sym) 'end) + ;; Special end keyword is in a class all it's own + (setq ltype 'end) + ;; If we found this in our keyword table, then it is a start + ;; of a block with a subtype. + (setq ltype 'block-start + stype (symbol-value sym))) + ;; Else - not a sym - just some random code. + (setq ltype 'code) + )))) + + ;; Looking at a close paren. + ((and (< 0 paren-depth) (looking-at "\\s)")) + (setq ltype 'close-paren)) ;; Last stand - drop in 'code' to say - yea, just some code. (t (setq ltype 'code)) @@ -194,10 +203,16 @@ Return list has these fields: ;; When the line ends with a comment. ;; Also tells us about continuations and comment start for lining up tail comments. (let ((csc (nth 8 ppsend))) + (when (and (not csc) (eq stype 'block-end)) + ;; block comment end lines must end in a comment, so record + ;; the beginning of that block comment instead. + (setq csc (nth 8 pps)) ) + + ;; If we have something, record what it is. (when csc (setq ec-col csc - ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis)) ;; type - )) + ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis))) ;; type + ) (list ltype stype cc paren-depth paren-char paren-col paren-delta ec-type ec-col) ))) @@ -231,16 +246,61 @@ These are more expensive checks queued off of a lvl1 context." "Return t if the current line is a comment based on LVL1 cache." (and (matlab-line-comment-p lvl1) (eq (nth mlf-stype lvl1) 'indent-ignore))) +(defsubst matlab-line-comment-style (lvl1) + "Return type type of comment on this line." + (and (matlab-line-comment-p lvl1) (nth mlf-stype lvl1))) + +(defsubst matlab-line-end-comment-column (lvl1) + "Return column of comment on line, or nil if no comment. +All lines that start with a comment end with a comment." + (when (eq (nth mlf-end-comment-type lvl1) 'comment) + (save-excursion + (goto-char (nth mlf-end-comment-col lvl1)) + (current-column)))) + +(defsubst matlab-line-ellipsis-p (lvl1) + "Return if this line ends with a comment. +All lines that start with a comment end with a comment." + (eq (nth mlf-end-comment-type lvl1) 'ellipsis)) + +;; Code and Declarations (defsubst matlab-line-block-start-keyword-p (lvl1) "Return t if the current line starts with block keyword." (eq (car lvl1) 'block-start)) -;; Code and Declarations (defsubst matlab-line-declaration-p (lvl1) "If the current line is a declaration, return the column it starts on. Declarations are things like function or classdef." (and (matlab-line-block-start-keyword-p lvl1) (eq (nth mlf-stype lvl1) 'decl))) +(defsubst matlab-line-end-p (lvl1) + "Non nil If the current line starts with an end." + (eq (car lvl1) 'end)) + +(defsubst matlab-line-block-middle-p (lvl1) + "Non nil If the current line starts with a middle block keyword. +These are keywords like `else' or `catch'." + (and (eq (car lvl1) 'block-start) (eq (nth 1 lvl1) 'mid))) + +(defsubst matlab-line-block-case-p (lvl1) + "Non nil If the current line starts with a middle block keyword. +These are keywords like `else' or `catch'." + (and (eq (car lvl1) 'block-start) (eq (nth 1 lvl1) 'case))) + +;; Parenthetical blocks +(defsubst matlab-line-close-paren-p (lvl1) + "Non nil If the current line starts with closing paren (any type.)" + (eq (car lvl1) 'close-paren)) + +(defsubst matlab-line-close-paren-char (lvl1) + "Non nil If the current line starts with closing paren (any type.)" + (nth mlf-paren-char lvl1)) + +(defsubst matlab-line-close-paren-col (lvl1) + "Non nil If the current line starts with closing paren (any type.)" + (nth mlf-paren-col lvl1)) + + ;;; Scanning Accessor utilities ;; ;; some utilities require some level of buffer scanning to get the answer. @@ -319,7 +379,7 @@ Used to speed up repeated queries on the same set of lines.") (setq extraclose (format "%d>" (nth mlf-paren-delta lvl1))))) - (message "%s%s %s%s%d%s%s %s" (nth mlf-ltype lvl1) + (message "%s%s %s%s%d%s%s %s %s" (nth mlf-ltype lvl1) (format " %s" (or (nth mlf-stype lvl1) "")) ;; paren system extraopen @@ -330,6 +390,9 @@ Used to speed up repeated queries on the same set of lines.") (cond ((eq (nth mlf-end-comment-type lvl1) 'comment) "%") ((eq (nth mlf-end-comment-type lvl1) 'ellipsis) "...") (t "")) + (if (matlab-line-end-comment-column lvl1) + (format " %d" (matlab-line-end-comment-column lvl1)) + "") ) ) diff --git a/matlab.el b/matlab.el index 6dfebd8..0670796 100644 --- a/matlab.el +++ b/matlab.el @@ -1363,10 +1363,6 @@ All Key Bindings: (make-local-variable 'write-contents-functions) (add-hook 'write-contents-functions 'matlab-mode-verify-fix-file-fn) - ;; DELETE - ;; when a buffer changes, flush parsing data. - ;;(add-hook 'after-change-functions 'matlab-change-function nil t) - ;; give each file it's own parameter history (make-local-variable 'matlab-shell-save-and-go-history) @@ -2513,44 +2509,35 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (blockcomm nil) (tmp nil)) (cond - ;; BLOCK COMMENT END _or_ body prefixed with % - ((and (setq blockcomm (save-excursion - (back-to-indentation) - (matlab-block-comment-bounds))) - (or (matlab-ltype-block-comment-end) - (matlab-ltype-comm-noblock))) - (list 'comment (save-excursion - (goto-char (car blockcomm)) - (current-indentation))) - ) - ;; BLOCK COMMENT START - ((matlab-ltype-block-comment-start) - (list 'comment (+ ci matlab-comment-anti-indent)) - ) - ;; BLOCK COMMENT BODY. - (blockcomm - (list 'comment - (+ (save-excursion - (goto-char (car blockcomm)) - (current-indentation)) - 2)) - ) ;; COMMENTS ((matlab-line-comment-p lvl1) - (cond - ;; HELP COMMENT and COMMENT REGION - ((setq tmp (matlab-line-comment-help-p lvl1)) - (list 'comment-help tmp)) - ;; COMMENT REGION comments - ((matlab-line-comment-ignore-p lvl1) - (list 'comment-ignore 0)) - ;; COMMENT Continued From Previous Line - ((setq tmp (matlab-ltype-continued-comm)) - (list 'comment tmp)) - (t - (list 'comment (+ ci matlab-comment-anti-indent))))) + (let ((comment-style (matlab-line-comment-style lvl1))) + (cond + ;; BLOCK START is like regular comment + ((eq comment-style 'block-start) + ;; indent like code, but some users like anti-indent + (list 'comment (+ ci matlab-comment-anti-indent)) + ) + ;; BLOCK END undoes body indent + ((or (eq comment-style 'block-end) + (eq comment-style 'block-body-prefix)) ; body prefix has same lineup rule + (list 'comment (matlab-line-end-comment-column lvl1))) + ;; BLOCK BODY is indented slightly from the start. + ((eq comment-style 'block-body) + (list 'comment (+ 2 (matlab-line-end-comment-column lvl1)))) + ;; HELP COMMENT and COMMENT REGION + ((setq tmp (matlab-line-comment-help-p lvl1)) + (list 'comment-help tmp)) + ;; COMMENT REGION comments + ((matlab-line-comment-ignore-p lvl1) + (list 'comment-ignore 0)) + ;; COMMENT Continued From Previous Line + ((setq tmp (matlab-ltype-continued-comm)) ;;; TODO : REPLACE + (list 'comment tmp)) + (t + (list 'comment (+ ci matlab-comment-anti-indent)))))) ;; FUNCTION DEFINITION - ((matlab-ltype-function-definition) + ((matlab-line-declaration-p lvl1) (if matlab-functions-have-end ;; A function line has intrinsic indentation iff function bodies are ;; not indented and the function line is nested within another function. @@ -2565,7 +2552,8 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (setq ci 0)) (list 'function ci)) ;; END keyword - ((matlab-lattr-local-end) + ((matlab-line-end-p lvl1) + ;;(matlab-lattr-local-end) (let ((end-of-function (let ((matlab-functions-have-end t)) (save-excursion @@ -2608,7 +2596,8 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio matlab-indent-level)))))) (list 'blockend ci)) ;; ELSE/CATCH keywords - ((matlab-lattr-middle-block-cont) + ((matlab-line-block-middle-p lvl1) + ;(matlab-lattr-middle-block-cont) (let ((m (match-string 1))) (list 'blockmid (condition-case nil @@ -2619,7 +2608,8 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (current-column)) (error (error "Unmatched %s" m)))))) ;; CASE/OTHERWISE keywords - ((matlab-lattr-endless-block-cont) + ((matlab-line-block-case-p lvl1) + ;;(matlab-lattr-endless-block-cont) (list 'blockendless (condition-case nil (save-excursion @@ -2632,22 +2622,21 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio matlab-case-level))) (error (error "Unmatched case/otherwise part"))))) ;; End of a MATRIX - ((matlab-lattr-array-end) - (list 'array-end (save-excursion - (back-to-indentation) - (matlab-up-list -1) - (let* ((fc (following-char)) - (mi (assoc fc matlab-maximum-indents)) - (max (if mi (if (listp (cdr mi)) - (car (cdr mi)) (cdr mi)) - nil)) - (ind (if mi (if (listp (cdr mi)) - (cdr (cdr mi)) (cdr mi)) - nil))) - ;; apply the maximum limits. - (if (and ind (> (- (current-column) ci) max)) - (1- ind) ; decor - (current-column)))))) + ((matlab-line-close-paren-p lvl1) + ;;(matlab-lattr-array-end) + (list 'array-end (let* ((fc (matlab-line-close-paren-char lvl1)) ;;following-char)) + (pc (matlab-line-close-paren-col lvl1)) + (mi (assoc fc matlab-maximum-indents)) + (max (if mi (if (listp (cdr mi)) + (car (cdr mi)) (cdr mi)) + nil)) + (ind (if mi (if (listp (cdr mi)) + (cdr (cdr mi)) (cdr mi)) + nil))) + ;; apply the maximum limits. + (if (and ind (> (- pc ci) max)) + (1- ind) ; decor + pc)))) ;; Code lines ((and (not (matlab-lattr-array-cont)) (not (matlab-prev-line-cont))) diff --git a/tests/indents.m b/tests/indents.m index 5177f61..ed07181 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -30,7 +30,7 @@ function indents(a,b,stuff) % $$$ !!0 % $$$ special ignore comments - + has_nested_fcn(); % !!4 - after ignore comments @@ -137,13 +137,19 @@ function out = array_constant_decls() multinest = { [ 1 2 %!!4 3 4 ]; %!!20 { 5 6 7 ... %!!18 - 8 9 10 }; %!!20 + 8 9 10 ... %!!20 + }; %!!18 fcncall(10, ... %!!18 12, ... %!!26 [ 13 14; %!!26 15 16 ]) %!!28 } ; %!!16 + % Line starting with end inside parens + disp(Csep(1: ... %!!4 + end)); %!!14 + + % !!4 out = { A %!!4 Blong %!!12 @@ -160,13 +166,17 @@ function C = block_starts_in_comments_and_strings() if true % if true + % !!8 + else % !!4 + + % !!8 end % if true % see previous function % !!4 for x=1:length(C) % !!4 - + % !!8 end @@ -234,7 +244,7 @@ function B = continuations_and_block_comments % continuation-comment !!17 % !!4 -blank between this & continuation comment - + % !!4 - more comments end From 06c26b19e70b09096f2cc3e1883213bc48d365ab Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 10 Mar 2021 22:15:01 -0500 Subject: [PATCH 043/168] matlab.el: (matlab-beginning-of-command): In comment skip part, also look for help comments. (matlab-next-line-indentation): Replace several -lattr- type commands with new predicates from matlab-scan.el. matlab-scan.el: (matlab-line-regular-comment-p): New --- matlab-scan.el | 4 ++++ matlab.el | 31 +++++++++++++++++++------------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 060d0c9..998b1f2 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -242,6 +242,10 @@ These are more expensive checks queued off of a lvl1 context." "Return t if the current line is a comment based on LVL1 cache." (eq (car lvl1) 'comment)) +(defsubst matlab-line-regular-comment-p (lvl1) + "Return t if the current line is a boring style comment based on LVL1 cache." + (and (eq (car lvl1) 'comment) (eq (nth 1 lvl1) nil))) + (defsubst matlab-line-comment-ignore-p (lvl1) "Return t if the current line is a comment based on LVL1 cache." (and (matlab-line-comment-p lvl1) (eq (nth mlf-stype lvl1) 'indent-ignore))) diff --git a/matlab.el b/matlab.el index 0670796..ce2c5c1 100644 --- a/matlab.el +++ b/matlab.el @@ -2105,7 +2105,8 @@ Travels across continuations." ;; Using forward-comment is very fast, and just skipps all comments until ;; we hit a line of code. ;; NOTE: This may fail with poorly indented code. - (when (matlab-ltype-continued-comm) + (when (or (matlab-ltype-help-comm) + (matlab-ltype-continued-comm)) (forward-comment -100000)) ;; Now walk backward across continued code lines. @@ -2766,22 +2767,27 @@ Assume that the following line does not contribute its own indentation not indenting function bodies. See `matlab-calculate-indentation'." (matlab-navigation-syntax - (let ((startpnt (point-at-eol))) + (let ((startpnt (point-at-eol)) + (lvl1 nil) + ) (save-excursion (matlab-with-current-command ;;(matlab-beginning-of-command) (goto-char (point-min)) (back-to-indentation) + (setq lvl1 (matlab-compute-line-context 1)) (let ((cc (or (matlab-lattr-block-close startpnt) 0)) - (end (matlab-lattr-local-end)) + (end (matlab-line-end-p lvl1)) ;(matlab-lattr-local-end)) (bc (matlab-lattr-block-cont startpnt)) - (mc (matlab-lattr-middle-block-cont)) - (ec (matlab-lattr-endless-block-cont)) - (hc (and (matlab-indent-function-body-p) (matlab-ltype-help-comm))) + (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) + (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) + (hc (and (matlab-indent-function-body-p) + (matlab-line-comment-help-p lvl1))) ;(matlab-ltype-help-comm))) (rc (and (/= 0 matlab-comment-anti-indent) - (matlab-ltype-comm-noblock) - (not (matlab-ltype-help-comm)) - (not (matlab-ltype-continued-comm)))) + (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) + ;;(not (matlab-ltype-help-comm)) + (not (matlab-ltype-continued-comm)) + )) (ci (current-indentation))) ;; When the current point is on a line with a function, the value of bc will ;; reflect the function in a block count iff if matlab-functions-have-end is @@ -2794,8 +2800,8 @@ See `matlab-calculate-indentation'." (if matlab-functions-have-end (if (and (not (matlab-indent-function-body-p)) - (or (matlab-ltype-function-definition) - (and (matlab-lattr-local-end) + (or (matlab-line-declaration-p lvl1) ;(matlab-ltype-function-definition) + (and (matlab-line-end-p lvl1) ;(matlab-lattr-local-end) (save-excursion (matlab-backward-sexp t) (looking-at "function\\b"))))) @@ -2804,7 +2810,8 @@ See `matlab-calculate-indentation'." (if (>= ci matlab-indent-level) (setq bc -1)))) ;; Else, funtions don't have ends in this file. - (if (and (matlab-indent-function-body-p) (matlab-ltype-function-definition)) + (if (and (matlab-indent-function-body-p) + (matlab-line-declaration-p lvl1)) ; (matlab-ltype-function-definition)) (setq bc (1+ bc)))) ;; Remove 1 from the close count if there is an END on the beginning ;; of this line, since in that case, the unindent has already happened. From 52775e4af3a50bbba65951021efe3ac049944f0b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 08:00:34 -0500 Subject: [PATCH 044/168] matlab.el: matlab-scan.el: (mlf-entity-start, mlf-paren-outer-char, mlf-paren-outer-point): New const, renumber the others. (mlf-paren-inner-char, mlf-paren-inner-col): Renamed fro versions w/out -inner in the name. (matlab-compute-line-context-lvl-1): Fill in entity start, and outer char/point. (matlab-line-block-comment-start): New (matlab-line-close-paren-inner-col, -char): Renamed to include -inner (matlab-line-close-paren-outer-char, -point): New (matlab-line-comment-help-p): Renamed to (matlab-scan-comment-help-p): Option PT now specifies if a point is returned instead of a column. (matlab-scan-previous-line-ellipsis-p): New (matlab-scan-beginning-of-command): New (matlab-describe-line-indent-context): Fixup names, etc. --- matlab-scan.el | 144 +++++++++++++++++++++++++++++++++++-------------- matlab.el | 64 +++++++++++----------- 2 files changed, 136 insertions(+), 72 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 998b1f2..3ec728b 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -86,30 +86,22 @@ return the cache if it finds it." (defconst mlf-ltype 0) (defconst mlf-stype 1) (defconst mlf-indent 2) -(defconst mlf-paren-depth 3) -(defconst mlf-paren-char 4) -(defconst mlf-paren-col 5) -(defconst mlf-paren-delta 6) -(defconst mlf-end-comment-type 7) -(defconst mlf-end-comment-col 8) +(defconst mlf-entity-start 3) +(defconst mlf-paren-depth 4) +(defconst mlf-paren-inner-char 5) +(defconst mlf-paren-inner-col 6) +(defconst mlf-paren-outer-char 7) +(defconst mlf-paren-outer-point 8) +(defconst mlf-paren-delta 9) +(defconst mlf-end-comment-type 10) +(defconst mlf-end-comment-col 11) (defun matlab-compute-line-context-lvl-1 () "Compute and return the level1 context for the current line of MATLAB code. Level 1 contexts are things quickly derived from `syntax-ppss' and other simple states. Computes multiple styles of line by checking for multiple types of context -in a single call using fastest methods. -Return list has these fields: - 0 - Primary line type - 1 - Secondary line type - 2 - Indentation - 3 - Parenthisis depth - 4 - Char for innermost beginning paren - 5 - Column of innermost beginning paren - 6 - Parenthisis depth change on this line - 7 - End Comment type (ellipsis, comment, or nil) - 8 - End Comment start column -" +in a single call using fastest methods." (save-excursion (back-to-indentation) (let* ((ppsend (save-excursion (syntax-ppss (point-at-eol)))) @@ -117,13 +109,16 @@ Return list has these fields: (ltype 'empty) (stype nil) (cc 0) + (start (point)) (paren-depth (nth 0 pps)) - (paren-char nil) - (paren-col nil) + (paren-inner-char nil) + (paren-inner-col nil) + (paren-outer-char nil) + (paren-outer-point nil) (paren-delta (- (car pps) (car ppsend))) (ec-type nil) (ec-col nil) - + (cont-from-prev nil) ) ;; This means we are somewhere inside a cell, array, or arg list. @@ -133,8 +128,10 @@ Return list has these fields: (when (> (nth 0 pps) 0) (save-excursion (goto-char (car (last (nth 9 pps)))) - (setq paren-char (char-after (point)) - paren-col (current-column)))) + (setq paren-inner-char (char-after (point)) + paren-inner-col (current-column) + paren-outer-point (car (nth 9 pps)) + paren-outer-char (char-after paren-outer-point) ))) (cond ;; For comments - We can only ever be inside a block comment, so @@ -146,7 +143,8 @@ Return list has these fields: 'block-end) ((looking-at "%") 'block-body-prefix) - (t 'block-body)))) + (t 'block-body)) + start (nth 8 pps))) ;; If indentation lands on end of line, this is an empty line ;; so nothing left to do. Keep after block-comment-body check @@ -214,7 +212,9 @@ Return list has these fields: ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis))) ;; type ) - (list ltype stype cc paren-depth paren-char paren-col paren-delta ec-type ec-col) + (list ltype stype cc start paren-depth + paren-inner-char paren-inner-col paren-outer-char paren-outer-point paren-delta + ec-type ec-col cont-from-prev) ))) @@ -267,6 +267,13 @@ All lines that start with a comment end with a comment." All lines that start with a comment end with a comment." (eq (nth mlf-end-comment-type lvl1) 'ellipsis)) +(defsubst matlab-line-block-comment-start (lvl1) + "Return the start of the block comment we are in, or nil." + (when (and (matlab-line-comment-p lvl1) + (memq (nth mlf-stype lvl1) + '(block-start block-end block-body block-body-prefix))) + (nth mlf-entity-start lvl1))) + ;; Code and Declarations (defsubst matlab-line-block-start-keyword-p (lvl1) "Return t if the current line starts with block keyword." @@ -296,32 +303,87 @@ These are keywords like `else' or `catch'." "Non nil If the current line starts with closing paren (any type.)" (eq (car lvl1) 'close-paren)) -(defsubst matlab-line-close-paren-char (lvl1) - "Non nil If the current line starts with closing paren (any type.)" - (nth mlf-paren-char lvl1)) +(defsubst matlab-line-close-paren-inner-char (lvl1) + "Return the paren character for the parenthetical expression LVL1 is in." + (nth mlf-paren-inner-char lvl1)) -(defsubst matlab-line-close-paren-col (lvl1) - "Non nil If the current line starts with closing paren (any type.)" - (nth mlf-paren-col lvl1)) +(defsubst matlab-line-close-paren-inner-col (lvl1) + "Return the paren column for the prenthetical expression LVL1 is in." + (nth mlf-paren-inner-col lvl1)) + +(defsubst matlab-line-close-paren-outer-char (lvl1) + "The paren character for the outermost prenthetical expression LVL1 is in." + (nth mlf-paren-outer-char lvl1)) + +(defsubst matlab-line-close-paren-outer-point (lvl1) + "The poit the outermost parenthetical expression start is at." + (nth mlf-paren-outer-point lvl1)) ;;; Scanning Accessor utilities ;; -;; some utilities require some level of buffer scanning to get the answer. +;; Some utilities require some level of buffer scanning to get the answer. ;; Keep those separate so they can depend on the earlier decls. -(defun matlab-line-comment-help-p (lvl1) +(defun matlab-scan-comment-help-p (lvl1 &optional pt) "Return declaration column if the current line is part of a help comment. Declarations are things like functions and classdefs. -Indentation a help comment depends on the column of the declaration." +Indentation a help comment depends on the column of the declaration. +Optional PT, if non-nil, means return the point instead of column" (and (matlab-line-comment-p lvl1) (save-excursion (beginning-of-line) (forward-comment -100000) (let ((c-lvl1 (matlab-compute-line-context 1))) (when (matlab-line-declaration-p c-lvl1) - (current-indentation))) + (if pt (point) (current-indentation)))) ))) +(defun matlab-scan-previous-line-ellipsis-p () + "Return the point of the previous line's continuation if there is one. +This is true iff the previous line has an ellipsis, but not if this line +is in an array with an implied continuation." + (save-excursion + (beginning-of-line) + (when (not (bobp)) + (forward-char -1) + (let* ((pps (syntax-ppss (point))) + (csc (nth 8 pps))) + ;; If the comment active on eol does NOT start with %, then it must be + ;; and ellipsis. + (and (/= (char-after csc) ?\%) + csc))))) + +(defun matlab-scan-beginning-of-command (&optional lvl1) + "Return point in buffer at the beginning of this command. +This function walks up any enclosing parens, and skips +backward over lines that include ellipsis." + (unless lvl1 (setq lvl1 (matlab-compute-line-context 1))) + ;; If we are in a block comment, just jump to the beginning, and + ;; that's it. + (let ((bcs (matlab-line-block-comment-start lvl1))) + (when bcs + (goto-char bcs) + (setq lvl1 (matlab-compute-line-context 1))) + + ;; If we are in a help comment, jump over that first. + (setq bcs (matlab-scan-comment-help-p lvl1 'point)) + (when bcs + (goto-char bcs) + (setq lvl1 (matlab-compute-line-context 1))) + + ;; Now scan backward till we find the beginning. + (let ((found nil)) + (while (not found) + ;; first - just jump to our outermost point. + (goto-char (or (matlab-line-close-paren-outer-point lvl1) (point))) + ;; Second - is there an ellipsis on prev line? + (let ((prev (matlab-scan-previous-line-ellipsis-p))) + (if (not prev) + (setq found t) + ;; Move to prev location if not found. + (goto-char prev)))) + (back-to-indentation) + (point)))) ;;; Caching ;; @@ -363,13 +425,13 @@ Used to speed up repeated queries on the same set of lines.") "Describe the indentation context for the current line." (interactive) (let* ((lvl1 (matlab-compute-line-context 1)) - (paren-char (nth mlf-paren-char lvl1)) - (open (format "%c" (or paren-char ?\())) + (paren-inner-char (nth mlf-paren-inner-char lvl1)) + (open (format "%c" (or paren-inner-char ?\())) (close (format "%c" - (cond ((not paren-char) ?\)) - ((= paren-char ?\() ?\)) - ((= paren-char ?\[) ?\]) - ((= paren-char ?\{) ?\}) + (cond ((not paren-inner-char) ?\)) + ((= paren-inner-char ?\() ?\)) + ((= paren-inner-char ?\[) ?\]) + ((= paren-inner-char ?\{) ?\}) (t ??)))) (extraopen "") (extraclose "") diff --git a/matlab.el b/matlab.el index ce2c5c1..6f7ff00 100644 --- a/matlab.el +++ b/matlab.el @@ -2088,37 +2088,39 @@ If `matlab-functions-have-end', skip over functions with end." (goto-char (match-end 0)) (current-word))))) -(defun matlab-beginning-of-command () +(defun matlab-beginning-of-command (&optional arg) "Go to the beginning of an M command. Travels across continuations." - (interactive) - (beginning-of-line) - (save-match-data - (let ((p nil) - (bc (matlab-block-comment-bounds))) - ;; block comment - just go to the beginning. - (if bc - (goto-char (car bc)) + (interactive "P") + (if arg + (matlab-scan-beginning-of-command) + (beginning-of-line) + (save-match-data + (let ((p nil) + (bc (matlab-block-comment-bounds))) + ;; block comment - just go to the beginning. + (if bc + (goto-char (car bc)) - ;; ELSE : Scan across lines that are related. - ;; Step one, skip all comments indented as continutions of a previous. - ;; Using forward-comment is very fast, and just skipps all comments until - ;; we hit a line of code. - ;; NOTE: This may fail with poorly indented code. - (when (or (matlab-ltype-help-comm) - (matlab-ltype-continued-comm)) - (forward-comment -100000)) + ;; ELSE : Scan across lines that are related. + ;; Step one, skip all comments indented as continutions of a previous. + ;; Using forward-comment is very fast, and just skipps all comments until + ;; we hit a line of code. + ;; NOTE: This may fail with poorly indented code. + (when (or (matlab-ltype-help-comm) + (matlab-ltype-continued-comm)) + (forward-comment -100000)) - ;; Now walk backward across continued code lines. - (while (and (or (setq p (matlab-lattr-array-cont)) ;; do this first b/c fast - (matlab-prev-line-cont) - ;; We used to do this, now handled w/ forward-comment above. - ;;(matlab-ltype-continued-comm) - ) - (save-excursion (beginning-of-line) (not (bobp)))) - (if p (goto-char p) (matlab-prev-line)) - (setq p nil))) - (back-to-indentation)))) + ;; Now walk backward across continued code lines. + (while (and (or (setq p (matlab-lattr-array-cont)) ;; do this first b/c fast + (matlab-prev-line-cont) + ;; We used to do this, now handled w/ forward-comment above. + ;;(matlab-ltype-continued-comm) + ) + (save-excursion (beginning-of-line) (not (bobp)))) + (if p (goto-char p) (matlab-prev-line)) + (setq p nil))) + (back-to-indentation))))) (defun matlab-end-of-command () "Go to the end of an M command. @@ -2527,7 +2529,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio ((eq comment-style 'block-body) (list 'comment (+ 2 (matlab-line-end-comment-column lvl1)))) ;; HELP COMMENT and COMMENT REGION - ((setq tmp (matlab-line-comment-help-p lvl1)) + ((setq tmp (matlab-scan-comment-help-p lvl1)) (list 'comment-help tmp)) ;; COMMENT REGION comments ((matlab-line-comment-ignore-p lvl1) @@ -2625,8 +2627,8 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio ;; End of a MATRIX ((matlab-line-close-paren-p lvl1) ;;(matlab-lattr-array-end) - (list 'array-end (let* ((fc (matlab-line-close-paren-char lvl1)) ;;following-char)) - (pc (matlab-line-close-paren-col lvl1)) + (list 'array-end (let* ((fc (matlab-line-close-paren-inner-char lvl1)) ;;following-char)) + (pc (matlab-line-close-paren-inner-col lvl1)) (mi (assoc fc matlab-maximum-indents)) (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) @@ -2782,7 +2784,7 @@ See `matlab-calculate-indentation'." (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) (hc (and (matlab-indent-function-body-p) - (matlab-line-comment-help-p lvl1))) ;(matlab-ltype-help-comm))) + (matlab-scan-comment-help-p lvl1))) ;(matlab-ltype-help-comm))) (rc (and (/= 0 matlab-comment-anti-indent) (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) ;;(not (matlab-ltype-help-comm)) From ba7df0119728ce556170e91aafecf4aaea21b116 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 19:43:05 -0500 Subject: [PATCH 045/168] tests/metest.el: Revise to move all message output outside what is being timed. Move looping over files outside the test functions. Combine timings from one test point to one number to be recorded. Reduce # of lines output during test running. --- tests/metest.el | 149 ++++++++++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 68 deletions(-) diff --git a/tests/metest.el b/tests/metest.el index 4d16604..bc6f0f7 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -43,18 +43,35 @@ (defun metest-all-syntax-tests () "Run all the syntax tests in this file." (metest-log-init) - - (metest-timeit 'metest-end-detect-test) - (metest-timeit 'metest-comment-string-syntax-test) - (metest-timeit 'metest-sexp-counting-test) - (metest-timeit 'metest-sexp-traversal-test) - (metest-timeit 'metest-indents-test) - (metest-timeit 'metest-parse-test) - (setq debug-on-error t) + (setq debug-on-error t) + + (metest-run 'metest-end-detect-test) + (metest-run 'metest-comment-string-syntax-test) + (metest-run 'metest-sexp-counting-test) + (metest-run 'metest-sexp-traversal-test) + (metest-run 'metest-indents-test) + (metest-run 'metest-parse-test) + (metest-log-report (metest-log-write)) ) +(defun metest-run (test) + "Run and time TEST." + (let* ((config (symbol-value test)) + (name (if (stringp config) config (car config))) + (files (or (cdr-safe config) '(""))) + (strlen (apply 'max (mapcar 'length files)))) + (message ">> Starting %s loop on %S" name files) + (dolist (F files) + (princ (format (concat "<< %s %-" (number-to-string strlen) "s ") name F) 'external-debugging-output) + (let ((out (metest-timeit test F))) + (when (listp out) + (princ (format "passed: %s %.2f s\n" (cdr out) (car out)) 'external-debugging-output) + ) + )) + (message ""))) + (defmacro metest-condition-case-error-msg (&rest forms) "Run FORMS, capturing any errors and associating with (point)." (declare (indent 0) (debug t)) @@ -67,44 +84,46 @@ (defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" ) "List of files for running end detection tests on.") -(defun metest-end-detect-test () +(defvar metest-end-detect-test (cons "END detection" met-end-detect-files)) +(defun metest-end-detect-test (F) "Run a test to make sure we correctly detect the state of managing 'end'." - (dolist (F met-end-detect-files) - (let ((buf (metest-find-file F)) - (cnt 0)) - (with-current-buffer buf - (goto-char (point-min)) - (message ">> Checking END detection in %S" (current-buffer)) - (if (re-search-forward "%%%\\s-*\\(\\w+\\)\\s-+\\(\\w+\\)$" nil t) - (let ((st-expect (intern (match-string-no-properties 1))) - (end-expect (intern (match-string-no-properties 2))) - (st-actual (matlab-guess-script-type)) - (end-actual (matlab-do-functions-have-end-p))) - (unless (eq st-actual st-expect) - (metest-error "Script type detection failure: Expected %s but found %s" - st-expect st-actual)) - (unless (eq end-actual end-expect) - (metest-error "Script end detection failure: Expected %s but found %s" - end-expect end-actual)) + (let ((buf (metest-find-file F)) + (ret nil) + (cnt 0)) + (with-current-buffer buf + (goto-char (point-min)) + ;;(message ">> Checking END detection in %S" (current-buffer)) + (if (re-search-forward "%%%\\s-*\\(\\w+\\)\\s-+\\(\\w+\\)$" nil t) + (let ((st-expect (intern (match-string-no-properties 1))) + (end-expect (intern (match-string-no-properties 2))) + (st-actual (matlab-guess-script-type)) + (end-actual (matlab-do-functions-have-end-p))) + (unless (eq st-actual st-expect) + (metest-error "Script type detection failure: Expected %s but found %s" + st-expect st-actual)) + (unless (eq end-actual end-expect) + (metest-error "Script end detection failure: Expected %s but found %s" + end-expect end-actual)) - (message "<< Script type and end detection passed: %s, %s" st-actual end-actual) - ) - ;; No expected values found in the file. - (metest-error "Test file did not include expected script-type cookie") - )))) - (message "")) + (setq ret (list st-actual end-actual)) + ;;(message "<< Script type and end detection passed: %s, %s" st-actual end-actual) + ) + ;; No expected values found in the file. + (metest-error "Test file did not include expected script-type cookie") + )) + ret)) (defvar met-stringtest-files '("stringtest.m") "List of files for running string tests on.") -(defun metest-comment-string-syntax-test () +(defvar metest-comment-string-syntax-test (cons "string/comment detection" met-stringtest-files)) +(defun metest-comment-string-syntax-test (F) "Run a test to make sure string nd comment highlighting work." - (dolist (F met-stringtest-files) (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) - (message ">> Starting string/comment detect loop in %S" (current-buffer)) + ;;(message ">> Starting string/comment detect loop in %S" (current-buffer)) (while (re-search-forward "#\\([csveb]\\)#" nil t) (goto-char (match-end 1)) (let ((md (match-data)) @@ -134,21 +153,20 @@ (setq cnt (1+ cnt)) )) (kill-buffer buf)) - (message "<< Comment and string syntax test: %d points passed" cnt) - )) - (message "")) + + (list cnt "tests"))) (defvar met-sexptest-files '("expressions.m" "mclass.m" "blocks.m") "List of files for running syntactic expression tests.") -(defun metest-sexp-counting-test () +(defvar metest-sexp-counting-test (cons "sexp counting" met-sexptest-files)) +(defun metest-sexp-counting-test (F) "Run a test to make sure string and comment highlighting work." - (dolist (F met-sexptest-files) (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) - (message ">> Starting sexp counting loop in %S" (current-buffer)) + ;;(message ">> Starting sexp counting loop in %S" (current-buffer)) (while (re-search-forward "#\\([0-9]\\)#" nil t) (save-excursion (goto-char (match-beginning 0)) @@ -175,18 +193,16 @@ (end-of-line) (setq cnt (1+ cnt)))) (kill-buffer buf) - (message "<< Sexp counting syntax test: %d points passed" cnt) - )) - (message "")) + (list cnt "tests"))) -(defun metest-sexp-traversal-test () +(defvar metest-sexp-traversal-test (cons "sexp traversal" met-sexptest-files)) +(defun metest-sexp-traversal-test (F) "Run a test to make sure high level block navigation works." - (dolist (F met-sexptest-files) (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) - (message ">> Starting sexp traversal loop in %S" (current-buffer)) + ;;(message ">> Starting sexp traversal loop in %S" (current-buffer)) (while (re-search-forward ">>\\([0-9]+\\)" nil t) (let* ((num (string-to-number (match-string 1))) (num2 0) @@ -210,23 +226,21 @@ (end-of-line) (setq cnt (1+ cnt)))) (kill-buffer buf) - (message "<< Sexp counting syntax test: %d points passed" cnt) - )) - (message "")) + (list cnt "test"))) (defvar met-indents-files '("indents.m" "mclass.m" "blocks.m" "mfuncends.m") "List of files for running syntactic indentation tests.") -(defun metest-indents-test () +(defvar metest-indents-test (cons "indenting" met-indents-files)) +(defun metest-indents-test (F) "Run a test to make sure high level block navigation works." - (dolist (F met-indents-files) (let ((buf (metest-find-file F)) (cnt 0)) (with-current-buffer buf (goto-char (point-min)) ;; (indent-region (point-min) (point-max)) - (message ">> Starting indents loop in %S" (current-buffer)) + ;;(message ">> Starting indents loop in %S" (current-buffer)) (while (re-search-forward "!!\\([0-9]+\\)" nil t) (let* ((num (string-to-number (match-string 1))) (calc (metest-condition-case-error-msg @@ -239,17 +253,14 @@ (end-of-line) (setq cnt (1+ cnt)))) (kill-buffer buf) - (message "<< Indentation syntax test: %s points passed" cnt) - )) - (message "")) + (list cnt "tests"))) (defvar met-parser-files '("mpclass.m") "List of files for running semantic parsing tests.") -(defun metest-parse-test () +(defvar metest-parse-test (cons "semantic parser" met-parser-files)) +(defun metest-parse-test (F) "Run the semantic parsing test to make sure the parse works." - - (dolist (F met-parser-files) (let ((buf (metest-find-file F)) exp act (cnt 0)) @@ -261,7 +272,7 @@ ;; Do the test (goto-char (point-min)) - (message ">> Starting semantic parser test in %S" (current-buffer)) + ;;(message ">> Starting semantic parser test in %S" (current-buffer)) (unless (re-search-forward "^%%\\s-*>>\\s-+SEMANTIC TEST" nil t) (metest-error "Semantic parser test: Failed to find test cookie.")) @@ -285,8 +296,7 @@ exp act)) ) - - (message ">> Semantic parser test: %d tags matched" cnt)))) + (list cnt "tests"))) (defun metest-compare-tags (EXP ACT) @@ -365,15 +375,18 @@ Do error checking to provide easier debugging." base (cdr base))) )) -(defun metest-timeit (fcn) +(defun metest-timeit (fcn &optional file) "Time running FCN and save result in LOGFILE. Use this to track perforamnce improvements during development automatically." (let* ((start (current-time)) - (out (funcall fcn)) - (end (current-time))) - (push (cons fcn (float-time (time-subtract end start))) - metest-time-log) - )) + (out (funcall fcn file)) + (end (current-time)) + (diff (float-time (time-subtract end start)))) + (if (eq fcn (car-safe (car-safe metest-time-log))) + ;; Same fcn, append our number + (setcdr (car metest-time-log) (+ diff (cdr (car metest-time-log)))) + (push (cons fcn diff) metest-time-log)) + (cons diff out))) (provide 'metest) From 71fe52530876578b556a4055d3a2f891dd273e82 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 20:06:00 -0500 Subject: [PATCH 046/168] matlab-scan.el: (matlab-describe-line-indent-context): Improve output by using new slots in lvl1 scan data. --- matlab-scan.el | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 3ec728b..0f68f1c 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -108,7 +108,7 @@ in a single call using fastest methods." (pps (syntax-ppss (point))) (ltype 'empty) (stype nil) - (cc 0) + (cc (current-indentation)) (start (point)) (paren-depth (nth 0 pps)) (paren-inner-char nil) @@ -424,6 +424,7 @@ Used to speed up repeated queries on the same set of lines.") (defun matlab-describe-line-indent-context () "Describe the indentation context for the current line." (interactive) + (back-to-indentation) (let* ((lvl1 (matlab-compute-line-context 1)) (paren-inner-char (nth mlf-paren-inner-char lvl1)) (open (format "%c" (or paren-inner-char ?\())) @@ -433,25 +434,46 @@ Used to speed up repeated queries on the same set of lines.") ((= paren-inner-char ?\[) ?\]) ((= paren-inner-char ?\{) ?\}) (t ??)))) + (innerparenstr (format "%s%d%s" open (nth mlf-paren-depth lvl1) close)) + (outerp-char (nth mlf-paren-outer-char lvl1)) + (outerp-open (if outerp-char (format "%c" outerp-char) "")) + (outerp-close (if (not outerp-char) "" + (format "%c" + (cond ((= outerp-char ?\() ?\)) + ((= outerp-char ?\[) ?\]) + ((= outerp-char ?\{) ?\}) + (t ??))))) + (outerparenopen "") + (outerparenclose "") (extraopen "") (extraclose "") ) - (when (= (nth mlf-paren-depth lvl1) 0) - (setq open (propertize open 'face 'shadow) - close (propertize close 'face 'shadow))) + (cond ((= (nth mlf-paren-depth lvl1) 0) + ;; 0 means no parens - so shade out parens to indicate. + (setq open (propertize open 'face 'shadow) + close (propertize close 'face 'shadow))) + ((<= (nth mlf-paren-depth lvl1) 1) + ;; If 1 or fewer parens, clear out outer chars + (setq outerp-open "" + outerp-close "")) + ((> (nth mlf-paren-depth lvl1) 2) + ;; If more than 2, signal more unknown parens in between + (setq outerp-open (concat outerp-open (string (decode-char 'ucs #x2026))) + outerp-close (concat (string (decode-char 'ucs #x2026)) outerp-close)))) (if (< (nth mlf-paren-delta lvl1) 0) (setq extraopen (format "<%d" (abs (nth mlf-paren-delta lvl1)))) (when (> (nth mlf-paren-delta lvl1) 0) (setq extraclose (format "%d>" (nth mlf-paren-delta lvl1))))) - (message "%s%s %s%s%d%s%s %s %s" (nth mlf-ltype lvl1) + (message "%s%s >>%d %s%s%s%s%s %s %s" (nth mlf-ltype lvl1) (format " %s" (or (nth mlf-stype lvl1) "")) + (nth mlf-indent lvl1) ;; paren system extraopen - open - (nth mlf-paren-depth lvl1) - close + outerp-open + innerparenstr + outerp-close extraclose (cond ((eq (nth mlf-end-comment-type lvl1) 'comment) "%") ((eq (nth mlf-end-comment-type lvl1) 'ellipsis) "...") From 023bbcf9f06a50eacbfbc6accc78e628cf2a27cd Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 20:07:33 -0500 Subject: [PATCH 047/168] matlab-syntax.el: (matlab--scan-line-bad-blockcomment) (matlab--scan-line-for-ellipsis): Remove narrowing to region and use search bound instead. This should be faster. (matlab--font-lock-syntactic-face): Quote in all the faces to avoid compiler warning. --- matlab-syntax.el | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index e0c35b4..677dc75 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -237,19 +237,15 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (defun matlab--scan-line-bad-blockcomment () "Scan this line for invalid block comment starts." - (save-restriction - (narrow-to-region (point-at-bol) (point-at-eol)) - (when (and (re-search-forward "%{" nil t) (not (looking-at "\\s-*$"))) - (goto-char (1- (match-end 0))) - t))) + (when (and (re-search-forward "%{" (point-at-eol) t) (not (looking-at "\\s-*$"))) + (goto-char (1- (match-end 0))) + t)) (defun matlab--scan-line-for-ellipsis () "Scan this line for an ellipsis." - (save-restriction - (narrow-to-region (point-at-bol) (point-at-eol)) - (when (re-search-forward "\\.\\.\\." nil t) - (goto-char (match-beginning 0)) - t))) + (when (re-search-forward "\\.\\.\\." (point-at-eol) t) + (goto-char (match-beginning 0)) + t)) ;;; Font Lock Support: ;; @@ -268,9 +264,9 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ;; This is a string. Check the start char to see if it was ;; marked as an unterminate string. (if (get-text-property (nth 8 pps) 'unterminated) - matlab-unterminated-string-face - font-lock-string-face) - font-lock-comment-face) + 'matlab-unterminated-string-face + 'font-lock-string-face) + 'font-lock-comment-face) ) ;;; From cf945dedd1af5adb602995477af2d104815e5f06 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 22:08:41 -0500 Subject: [PATCH 048/168] tests/metest.el: Improve control of debugging output by enabling for test script code, but disabling while running individual tests. Tests will catch the error, and show the line in the file where the test failed. --- tests/metest.el | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/metest.el b/tests/metest.el index bc6f0f7..0e0c45d 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -42,10 +42,10 @@ (defun metest-all-syntax-tests () "Run all the syntax tests in this file." + (setq debug-on-error t) + (metest-log-init) - (setq debug-on-error t) - (metest-run 'metest-end-detect-test) (metest-run 'metest-comment-string-syntax-test) (metest-run 'metest-sexp-counting-test) @@ -65,7 +65,10 @@ (message ">> Starting %s loop on %S" name files) (dolist (F files) (princ (format (concat "<< %s %-" (number-to-string strlen) "s ") name F) 'external-debugging-output) - (let ((out (metest-timeit test F))) + (let ((old debug-on-error) + (out (progn (setq debug-on-error nil) + (metest-timeit test F)))) + (setq debug-on-error old) (when (listp out) (princ (format "passed: %s %.2f s\n" (cdr out) (car out)) 'external-debugging-output) ) @@ -128,8 +131,8 @@ (goto-char (match-end 1)) (let ((md (match-data)) (mc (match-string 1)) - (bc (matlab-block-comment-bounds)) - (qd (matlab-cursor-comment-string-context))) + (bc (metest-condition-case-error-msg (matlab-block-comment-bounds))) + (qd (metest-condition-case-error-msg (matlab-cursor-comment-string-context)))) ;; Test 1 - what are we? (unless (or (and (string= "b" mc) bc) (and (string= "v" mc) (eq 'charvector qd)) @@ -174,7 +177,8 @@ (let* ((num (string-to-number (match-string 1)))) (save-restriction (narrow-to-region (point-at-bol) (point)) - (matlab-move-simple-sexp-internal (- num)) + (metest-condition-case-error-msg + (matlab-move-simple-sexp-internal (- num))) (skip-chars-backward " \t;.=%") (if (not (eq (point) (point-min))) (save-restriction @@ -209,7 +213,7 @@ (begin nil)) (skip-chars-forward " \n\t;%") (setq begin (point)) - (matlab-forward-sexp) + (metest-condition-case-error-msg (matlab-forward-sexp)) (skip-chars-forward " \n\t;%") (if (not (looking-at "<<\\([0-9]+\\)")) (metest-error "Failed to find matching test end token for %d" @@ -315,7 +319,7 @@ Do error checking to provide easier debugging." (defun metest-error (&rest args) "Produce an err with standardized file/line prefix." (declare (indent 1)) - (let ((pre (format "%s:%d: Error: " + (let ((pre (format "\n%s:%d: Error: " (file-name-nondirectory (buffer-file-name)) (line-number-at-pos))) (post (apply 'format args))) From 6fecb01874af6bf23c6079c39288ec8d854a7d95 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 22:09:44 -0500 Subject: [PATCH 049/168] tests/blocks.m: Indented blank lines. tests/indents.m: Indent blank lines. Added sample for fcn with very long name that uses array continuation in the argument list. --- tests/blocks.m | 6 +++--- tests/indents.m | 21 +++++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/blocks.m b/tests/blocks.m index 6fe061e..ba33bcd 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -66,15 +66,15 @@ classdef blocks < handle %>>17 function properties(~)%!!8 end %<<17 - + %>>18 function methods(~)%!!8 end %<<18 - + %>>19 function events(~)%!!8 end %<<19 - + %>>20 function events=arguments(arguments)%!!8 arguments, arguments(:,:) {mustBeNumeric}, end %!!12 diff --git a/tests/indents.m b/tests/indents.m index ed07181..6990653 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -80,9 +80,9 @@ function B = ends_in_comments_and_strings() end; B = [ 1 2 ... % is this the end? 3 4 ]; % !!15 - + % !!4 - + if foo A = E; @@ -92,7 +92,7 @@ function B = ends_in_comments_and_strings() code1(), ... code2(); %!!8 - + % NOTE: Blank space below should cancel the indent effect of ellipsis. code1() ... @@ -145,6 +145,15 @@ function out = array_constant_decls() 15 16 ]) %!!28 } ; %!!16 + % 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' %!!24 + 'str' %!!37 + 'str' %!!37 + }); %!!35 + % Line starting with end inside parens disp(Csep(1: ... %!!4 end)); %!!14 @@ -176,7 +185,7 @@ function C = block_starts_in_comments_and_strings() % see previous function % !!4 for x=1:length(C) % !!4 - + % !!8 end @@ -215,8 +224,8 @@ function B = continuations_and_block_comments % !!4 %} - % Block comment indicators MUST be on a line by themselves. - %{ Not a block comment } +% Block comment indicators MUST be on a line by themselves. +%{ Not a block comment } foo(1); % !!4 - don't indent this special From 7d5a73fb49a3344d360c79549973c206a1fbeca6 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 22:12:24 -0500 Subject: [PATCH 050/168] matlab-scan.el: (matlab-block-keyword-list): Add elseif. (matlab-compute-line-context): Change how cache is called. (mlf-indent renamed to mlf-point): (matlab-compute-line-context-lvl-1): Use above renamed. (matlab-line-code-p): New predicate. (matlab-scan-previous-line-ellipsis-p): Fix bug, return column instead. (matlab-scan-cache-max): New (matlab-scan-cache-get, matlab-scan-cache-put): Implement. --- matlab-scan.el | 64 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 0f68f1c..fa4aeb5 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -40,6 +40,7 @@ ("events" . mcos) ("enumeration" . mcos) ("if" . ctrl) + ("elseif" . mid) ("else" . mid) ("ifelse" . mid) ("for" . ctrl) @@ -74,10 +75,12 @@ LEVEL of 1 is the most primitive / simplest data. This function caches compuated context onto the line it is for, and will return the cache if it finds it." (cond ((= level 1) - (let ((ctxt (matlab-scan-cache-get 1))) + (let ((ctxt (save-excursion + (back-to-indentation) + (matlab-scan-cache-get)))) (unless ctxt (setq ctxt (matlab-compute-line-context-lvl-1)) - (matlab-scan-cache-put ctxt 1)) + (matlab-scan-cache-put ctxt)) ctxt)) (t nil)) @@ -85,7 +88,7 @@ return the cache if it finds it." (defconst mlf-ltype 0) (defconst mlf-stype 1) -(defconst mlf-indent 2) +(defconst mlf-point 2) (defconst mlf-entity-start 3) (defconst mlf-paren-depth 4) (defconst mlf-paren-inner-char 5) @@ -108,7 +111,7 @@ in a single call using fastest methods." (pps (syntax-ppss (point))) (ltype 'empty) (stype nil) - (cc (current-indentation)) + (pt (point)) (start (point)) (paren-depth (nth 0 pps)) (paren-inner-char nil) @@ -212,7 +215,7 @@ in a single call using fastest methods." ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis))) ;; type ) - (list ltype stype cc start paren-depth + (list ltype stype pt start paren-depth paren-inner-char paren-inner-col paren-outer-char paren-outer-point paren-delta ec-type ec-col cont-from-prev) ))) @@ -275,6 +278,10 @@ All lines that start with a comment end with a comment." (nth mlf-entity-start lvl1))) ;; Code and Declarations +(defsubst matlab-line-code-p (lvl1) + "Return t if the current line is boring old code." + (eq (car lvl1) 'code)) + (defsubst matlab-line-block-start-keyword-p (lvl1) "Return t if the current line starts with block keyword." (eq (car lvl1) 'block-start)) @@ -303,6 +310,10 @@ These are keywords like `else' or `catch'." "Non nil If the current line starts with closing paren (any type.)" (eq (car lvl1) 'close-paren)) +(defsubst matlab-line-paren-depth (lvl1) + "The current depth of parens at the start of this line" + (nth mlf-paren-depth lvl1)) + (defsubst matlab-line-close-paren-inner-char (lvl1) "Return the paren character for the parenthetical expression LVL1 is in." (nth mlf-paren-inner-char lvl1)) @@ -339,7 +350,7 @@ Optional PT, if non-nil, means return the point instead of column" ))) (defun matlab-scan-previous-line-ellipsis-p () - "Return the point of the previous line's continuation if there is one. + "Return the column of the previous line's continuation if there is one. This is true iff the previous line has an ellipsis, but not if this line is in an array with an implied continuation." (save-excursion @@ -350,8 +361,10 @@ is in an array with an implied continuation." (csc (nth 8 pps))) ;; If the comment active on eol does NOT start with %, then it must be ;; and ellipsis. - (and (/= (char-after csc) ?\%) - csc))))) + (and csc + (/= (char-after csc) ?\%) + (goto-char csc) + (current-column)))))) (defun matlab-scan-beginning-of-command (&optional lvl1) "Return point in buffer at the beginning of this command. @@ -391,16 +404,28 @@ backward over lines that include ellipsis." "Cache of recently computed line contexts. Used to speed up repeated queries on the same set of lines.") (make-variable-buffer-local 'matlab-scan-temporal-cache) +(defvar matlab-scan-cache-max 10 + "Largest size of the cache. +Larger means less computation, but more time scanning. +Since the list isn't sorted, not optimizations possible.") -(defun matlab-scan-cache-get (level) - "Get a cached context at level." - ;; TODO - nil) +(defun matlab-scan-cache-get () + "Get a cached context." + (let ((pt (point)) + (cache matlab-scan-temporal-cache)) + (while (and cache (/= pt (nth mlf-point (car cache)))) + (setq cache (cdr cache))) + ;; If we found a match, return it. + (when (and cache (= pt (nth mlf-point (car cache)))) + (car cache)))) -(defun matlab-scan-cache-put (ctxt level) - "Get a cached context at level." - ;; TODO - nil) +(defun matlab-scan-cache-put (ctxt) + "Put a context onto the cache. +Make sure the cache doesn't exceed max size." + (push ctxt matlab-scan-temporal-cache) + (setcdr (or (nthcdr matlab-scan-cache-max matlab-scan-temporal-cache) + (cons nil nil)) + nil)) (defun matlab-scan-after-change-fcn (start end length) @@ -411,13 +436,14 @@ Used to speed up repeated queries on the same set of lines.") "Setup use of the indent cache for the current buffer." (interactive) (add-hook 'after-change-functions 'matlab-scan-after-change-fcn t) - ) + (setq matlab-scan-temporal-cache nil)) (defun matlab-scan-disable () "Setup use of the indent cache for the current buffer." (interactive) (remove-hook 'after-change-functions 'matlab-scan-after-change-fcn t) - ) + (setq matlab-scan-temporal-cache nil)) + ;;; Debugging and Querying ;; @@ -468,7 +494,7 @@ Used to speed up repeated queries on the same set of lines.") (message "%s%s >>%d %s%s%s%s%s %s %s" (nth mlf-ltype lvl1) (format " %s" (or (nth mlf-stype lvl1) "")) - (nth mlf-indent lvl1) + (nth mlf-point lvl1) ;; paren system extraopen outerp-open From bc6b550961b13a943d5c2331b753c2fe84f1a7a6 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 12 Mar 2021 22:17:00 -0500 Subject: [PATCH 051/168] matlab.el: (matlab-beginning-of-command) (matlab-ltype-continued-comm): Replace some calls to use new calls from matlab-scan. (matlab-ltype-empty, matlab-ltype-comm-noblok) (matlab-ltype-comm, matlab-ltype-comm-ignore) (matlab-ltype-code, matlab-lattr-comm) (matlab-lattr-array-cont): Swap out old impl with new from matlab-scan. Replace some calls to use new calls from matlab-scan. (matlab-calculate-indentation-1): Replace old call with one from matlab-scan. --- matlab.el | 74 ++++++++++++++++++++++--------------------------------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/matlab.el b/matlab.el index 6f7ff00..851f517 100644 --- a/matlab.el +++ b/matlab.el @@ -2096,24 +2096,26 @@ Travels across continuations." (matlab-scan-beginning-of-command) (beginning-of-line) (save-match-data - (let ((p nil) - (bc (matlab-block-comment-bounds))) + (let* ((lvl1 (matlab-compute-line-context 1)) + (p nil) + (bc (matlab-line-block-comment-start lvl1)) ;(matlab-block-comment-bounds))) + ) ;; block comment - just go to the beginning. (if bc - (goto-char (car bc)) - + (goto-char bc) + ;; ELSE : Scan across lines that are related. ;; Step one, skip all comments indented as continutions of a previous. ;; Using forward-comment is very fast, and just skipps all comments until ;; we hit a line of code. ;; NOTE: This may fail with poorly indented code. - (when (or (matlab-ltype-help-comm) + (when (or (matlab-scan-comment-help-p lvl1) ;(matlab-ltype-help-comm) (matlab-ltype-continued-comm)) (forward-comment -100000)) ;; Now walk backward across continued code lines. - (while (and (or (setq p (matlab-lattr-array-cont)) ;; do this first b/c fast - (matlab-prev-line-cont) + (while (and (or (setq p (matlab-line-close-paren-outer-point (matlab-compute-line-context 1))) ;;(matlab-lattr-array-cont)) ;; do this first b/c fast + (matlab-scan-previous-line-ellipsis-p) ;; (matlab-prev-line-cont) ;; We used to do this, now handled w/ forward-comment above. ;;(matlab-ltype-continued-comm) ) @@ -2151,39 +2153,25 @@ Travells a cross continuations" (defun matlab-ltype-empty () ; blank line "Return t if current line is empty." - (save-excursion - (beginning-of-line) - (looking-at "^[ \t]*$"))) + (matlab-line-empty-p (matlab-compute-line-context 1))) (defun matlab-ltype-comm-noblock () "Return t if the current line is a MATLAB single-line comment. Returns nil for Cell start %% and block comments %{, %}. Used in `matlab-ltype-comm', but specialized for not cell start." - (save-excursion - (beginning-of-line) - (looking-at "[ \t]*%\\([^%{}]\\|$\\)"))) + (matlab-line-regular-comment-p (matlab-compute-line-context 1))) (defun matlab-ltype-comm () ; comment line "Return t if current line is a MATLAB comment line. Return the symbol 'cellstart if it is a double %%. Return the symbol 'blockcomm if we are in a block comment." - (save-excursion - (beginning-of-line) - (cond - ((or (matlab-ltype-block-comment-start) - (matlab-block-comment-bounds)) - 'blockcomm) - ((matlab-ltype-comm-noblock) - t) - ((looking-at "[ \t]*%%") - 'cellstart) - (t nil)))) + ;; Looking through uses, no one uses the extra type of output + ;; this provides, so replace with very simple call. + (matlab-line-comment-p (matlab-compute-line-context 1))) (defun matlab-ltype-comm-ignore () ; comment out a region line "Return t if current line is a MATLAB comment region line." - (save-excursion - (beginning-of-line) - (looking-at (concat "[ \t]*" matlab-comment-region-s)))) + (matlab-line-comment-ignore-p (matlab-compute-line-context 1))) (defun matlab-ltype-help-comm () "Return position of function decl if the point in a MATLAB help comment." @@ -2212,19 +2200,14 @@ Return the symbol 'blockcomm if we are in a block comment." "Return column of previous line's comment start, or nil." (save-excursion (beginning-of-line) - (let ((commtype (matlab-ltype-comm-noblock))) - (if (or (null commtype) + (let* ((lvl (matlab-compute-line-context 1))) + (if (or (null (matlab-line-regular-comment-p lvl)) (bobp)) nil ;; We use forward-line and not matlab-prev-line because ;; we want blank lines to terminate this indentation method. (forward-line -1) - (let ((col (matlab-lattr-comm))) - (if col - (progn - (goto-char col) - (current-column)) - nil)))))) + (matlab-line-end-comment-column (matlab-compute-line-context 1)))))) (defun matlab-ltype-function-definition () "Return t if the current line is a function definition." @@ -2234,11 +2217,13 @@ Return the symbol 'blockcomm if we are in a block comment." (defun matlab-ltype-code () ; line of code "Return t if current line is a MATLAB code line." - (and (not (matlab-ltype-empty)) (not (matlab-ltype-comm)))) + (matlab-line-code-p (matlab-compute-line-context 1))) + ;(and (not (matlab-ltype-empty)) (not (matlab-ltype-comm)))) (defun matlab-lattr-comm () ; line has comment "Return t if current line contain a comment." - (save-excursion (matlab-comment-on-line))) + (matlab-line-comment-p (matlab-compute-line-context 1))) + ;(save-excursion (matlab-comment-on-line))) (defun matlab-lattr-implied-continuation () "Return non-nil if this line has implied continuation on the next. @@ -2291,12 +2276,13 @@ based on what it ends with." (defun matlab-lattr-array-cont () "Return non-nil if current line is in an array. If the entirety of the array is on this line, return nil." - (condition-case nil - (save-excursion - (beginning-of-line) - (matlab-up-list -1) - (and (looking-at "[[{]") (point))) - (error nil))) + (matlab-line-close-paren-outer-point (matlab-compute-line-context 1))) +;; (condition-case nil +;; (save-excursion +;; (beginning-of-line) +;; (matlab-up-list -1) +;; (and (looking-at "[[{]") (point))) +;; (error nil))) (defun matlab-lattr-array-end () "Return non-nil if the current line closes an array. @@ -2660,7 +2646,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio (list 'code ci)) ;; Lines continued from previous statements. (t - (list (if (matlab-ltype-empty) 'empty + (list (if (matlab-line-empty-p lvl1) 'empty (if (matlab-lattr-array-cont) 'array-cont 'code)) ;; Record beginning of the command (let ((boc (save-excursion From 3f239aaefbfd87e4ad3aea21ce2011b6cfb24ab9 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 10:45:54 -0500 Subject: [PATCH 052/168] matlab-syntax.el: (matlab--put-char-category): If at end of buffer, don't put the text property there. --- matlab-syntax.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 677dc75..8ff591d 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -118,8 +118,9 @@ (defun matlab--put-char-category (pos category) "At character POS, put text CATEGORY." - (put-text-property pos (1+ pos) 'category category) - (put-text-property pos (1+ pos) 'mcm t) + (when (not (eobp)) + (put-text-property pos (1+ pos) 'category category) + (put-text-property pos (1+ pos) 'mcm t)) ) (defmacro matlab--syntax-symbol (symbol syntax doc) From 40c40e77a10c860ecb3d166a6f0d633333e6cb33 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 11:05:36 -0500 Subject: [PATCH 053/168] matlab.el: (matlab-find-prev-line): If 'ignorecomments' specified, use fast `forward-comment'. Otherwise, convert recursive find into iterative loop. (matlab-find-code-line): Replace with faster `forward-comment'. --- matlab.el | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/matlab.el b/matlab.el index 851f517..9315723 100644 --- a/matlab.el +++ b/matlab.el @@ -1488,11 +1488,21 @@ Return t on success, nil if we couldn't navigate backwards." (defun matlab-find-prev-line (&optional ignorecomments) "Recurse backwards until a code line is found." - (if (= -1 (forward-line -1)) nil - (if (or (matlab-ltype-empty) - (matlab-ltype-comm-ignore) - (and ignorecomments (matlab-ltype-comm))) - (matlab-find-prev-line ignorecomments) t))) + (if ignorecomments + ;; This version is now super easy, as this built-in + ;; skips comments and whitespace. Nil on bobp. + (progn + (beginning-of-line) + (forward-comment -100000) + (not (bobp))) + ;; Else, scan backward at least 1 step. nil if bob + (if (= -1 (forward-line -1)) nil + ;; Now scan backwards iteratively + (catch 'moose + (while (or (matlab-ltype-empty) (matlab-ltype-comm-ignore)) + (when (= -1 (forward-line -1)) + (throw 'moose nil))) + t)))) (defun matlab-prev-line () "Go to the previous line of code or comment. Return nil if not found." @@ -1504,12 +1514,15 @@ Return t on success, nil if we couldn't navigate backwards." "Walk forwards until we are on a line of code return t on success. If the currnet line is code, return immediately. Ignore comments and whitespace." - (if (or (matlab-ltype-empty) - (matlab-ltype-comm)) - (if (= 1 (forward-line 1)) - nil ;; end of buffer. - (matlab-find-code-line)) ;; try again. - t)) + (forward-comment 100000) + (not (eobp))) +;; Iterative version: +;; (catch 'moose +;; (while (or (matlab-ltype-empty) +;; (matlab-ltype-comm)) +;; (when (= 1 (forward-line 1)) +;; (throw 'moose nil))) ;; end of buffer. +;; t)) (defvar matlab-in-command-restriction nil From b2d29358e4c76961323e60bd90477adb9f9eb4e7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 11:33:52 -0500 Subject: [PATCH 054/168] matlab.el: (matlab-valid-end-construct-p): Replaced old impl with simple call to syntax-ppss. Seems to pass tests. --- matlab.el | 45 ++++++++------------------------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/matlab.el b/matlab.el index 9315723..10eb3ac 100644 --- a/matlab.el +++ b/matlab.el @@ -1548,43 +1548,14 @@ restricted." (defun matlab-valid-end-construct-p () "Return non-nil if the end after point terminates a block. Return nil if it is being used to dereference an array." - (let ((p (point)) - (err1 t)) - (if (eq (preceding-char) ?.) - ;; This is a struct field, not valid. - nil - (condition-case nil - (save-match-data - (matlab-with-current-command - ;; This used to add some sort of protection, but I don't know what - ;; the condition was, or why the simple case doesn't handle it. - ;; - ;; The above replacement fixes a case where a continuation in an array - ;; befuddles the identifier. - ;; (progn ;;(matlab-end-of-command (point)) - ;; (end-of-line) - ;; (if (> p (point)) - ;; (progn - ;; (setq err1 nil) - ;; (error))) - ;; (point)))) - (save-excursion - ;; beginning of param list - (matlab-up-list -1) - ;; backup over the parens. If that fails - (condition-case nil - (progn - (forward-sexp 1) - ;; If we get here, the END is inside parens, which is not a - ;; valid location for the END keyword. As such it is being - ;; used to dereference array parameters - nil) - ;; This error means that we have an unterminated paren - ;; block, so this end is currently invalid. - (error nil))))) - ;; an error means the list navigation failed, which also means we are - ;; at the top-level - (error err1))))) + (if (eq (preceding-char) ?.) + ;; This is a struct field, not valid. + nil + (let ((pps (syntax-ppss (point)))) + ;; If we are in a set of parenthisis, then not valid b/c it is + ;; likely an array reference. Valid == 0 paren depth. + (= (nth 0 pps) 0)))) + ;;; Regexps for MATLAB language =============================================== From dc0bafae3a15e45fbf8ab47aaf1380aa0baae6ba Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 13:17:15 -0500 Subject: [PATCH 055/168] matlab-scan.el: (matlab-compute-line-context): Add lvl2 support. (mlf-indent): New slot (mlf-end-comment-pt): Rename to match what it is. (matlab-compute-line-context-lvl-1): Add mlf-indent to output. (matlab-with-context-line): Macro to just jump to a context. (matlab-line-comment-ignore-p): Doc fix. (matlab-line-end-comment-column): Deal with rename of mlf-end-comment-pt. (matlab-line-end-of-code): New fcn. (mlf-level1 & others): New (matlab-compute-line-context-lvl-2): New (matlab-describe-line-indent-context): Also capture lvl2. --- matlab-scan.el | 288 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 200 insertions(+), 88 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index fa4aeb5..41bff83 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -68,11 +68,12 @@ ;; as quickly as possible. -(defun matlab-compute-line-context (level) +(defun matlab-compute-line-context (level &rest context) "Compute and return the line context for the current line of MATLAB code. LEVEL indicates how much information to return. LEVEL of 1 is the most primitive / simplest data. -This function caches compuated context onto the line it is for, and will +LEVEL of 2 is stuff that is derived from previous lines of code. +This function caches computed context onto the line it is for, and will return the cache if it finds it." (cond ((= level 1) (let ((ctxt (save-excursion @@ -82,22 +83,29 @@ return the cache if it finds it." (setq ctxt (matlab-compute-line-context-lvl-1)) (matlab-scan-cache-put ctxt)) ctxt)) + ((= level 2) + (apply 'matlab-compute-line-context-lvl-2 context)) (t nil)) ) - + +;;; LEVEL 1 SCANNER +;; +;; This scanner pulls out context available on the current line. +;; This inludes the nature of the line, and anything sytax-ppss gives is. (defconst mlf-ltype 0) (defconst mlf-stype 1) (defconst mlf-point 2) -(defconst mlf-entity-start 3) -(defconst mlf-paren-depth 4) -(defconst mlf-paren-inner-char 5) -(defconst mlf-paren-inner-col 6) -(defconst mlf-paren-outer-char 7) -(defconst mlf-paren-outer-point 8) -(defconst mlf-paren-delta 9) -(defconst mlf-end-comment-type 10) -(defconst mlf-end-comment-col 11) +(defconst mlf-indent 3) +(defconst mlf-entity-start 4) +(defconst mlf-paren-depth 5) +(defconst mlf-paren-inner-char 6) +(defconst mlf-paren-inner-col 7) +(defconst mlf-paren-outer-char 8) +(defconst mlf-paren-outer-point 9) +(defconst mlf-paren-delta 10) +(defconst mlf-end-comment-type 11) +(defconst mlf-end-comment-pt 12) (defun matlab-compute-line-context-lvl-1 () "Compute and return the level1 context for the current line of MATLAB code. @@ -112,6 +120,7 @@ in a single call using fastest methods." (ltype 'empty) (stype nil) (pt (point)) + (indent (current-indentation)) (start (point)) (paren-depth (nth 0 pps)) (paren-inner-char nil) @@ -215,27 +224,27 @@ in a single call using fastest methods." ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis))) ;; type ) - (list ltype stype pt start paren-depth + (list ltype stype pt indent start paren-depth paren-inner-char paren-inner-col paren-outer-char paren-outer-point paren-delta - ec-type ec-col cont-from-prev) + ec-type ec-col + ;;cont-from-prev + ) ))) - -(defun matlab-compute-line-context-lvl-2 (lvl1) - "Compute level 2 line contexts for indentation. -These are more expensive checks queued off of a lvl1 context." - - ;; matlab-ltype-help-comm - - ;; block start - ;; block end - ;; change in # blocks on this line. - - ) - -;;; Accessor Utilities +;;; Accessor Utilities for LEVEL 1 ;; ;; Use these to query a context for a piece of data +(defmacro matlab-with-context-line (__context &rest forms) + "Save excursion, and move point to the line specified by CONTEXT. +Takes a lvl1 or lvl2 context. +Returns the value from the last part of forms." + (declare (indent 1)) + `(save-excursion + ;; The CAR of a LVL2 is a LVL1. If __context is LVL1, then + ;; the car-safe will return nil + (goto-char (nth mlf-point (or (car-safe (car ,__context)) ,__context))) + ,@forms)) + (defsubst matlab-line-empty-p (lvl1) "Return t if the current line is empty based on LVL1 cache." (eq (car lvl1) 'empty)) @@ -250,7 +259,7 @@ These are more expensive checks queued off of a lvl1 context." (and (eq (car lvl1) 'comment) (eq (nth 1 lvl1) nil))) (defsubst matlab-line-comment-ignore-p (lvl1) - "Return t if the current line is a comment based on LVL1 cache." + "Return t if the current line is an indentation ignored comment." (and (matlab-line-comment-p lvl1) (eq (nth mlf-stype lvl1) 'indent-ignore))) (defsubst matlab-line-comment-style (lvl1) @@ -262,7 +271,7 @@ These are more expensive checks queued off of a lvl1 context." All lines that start with a comment end with a comment." (when (eq (nth mlf-end-comment-type lvl1) 'comment) (save-excursion - (goto-char (nth mlf-end-comment-col lvl1)) + (goto-char (nth mlf-end-comment-pt lvl1)) (current-column)))) (defsubst matlab-line-ellipsis-p (lvl1) @@ -305,6 +314,19 @@ These are keywords like `else' or `catch'." These are keywords like `else' or `catch'." (and (eq (car lvl1) 'block-start) (eq (nth 1 lvl1) 'case))) +(defsubst matlab-line-end-of-code (&optional lvl1) + "Go to the end of the code on the current line. +If there is a comment or ellipsis, go to the beginning of that. +If the line starts with a comment return nil, otherwise t." + (unless lvl1 (setq lvl1 (matlab-compute-line-context 1))) + (goto-char (nth mlf-point lvl1)) + (if (or (matlab-line-empty-p lvl1) (matlab-line-comment-p lvl1)) + nil + ;; Otherwise, look for that code. + (if (eq (nth mlf-end-comment-type lvl1) 'comment) + (goto-char (nth mlf-end-comment-pt lvl1)) + (goto-char (point-at-eol))))) + ;; Parenthetical blocks (defsubst matlab-line-close-paren-p (lvl1) "Non nil If the current line starts with closing paren (any type.)" @@ -329,7 +351,87 @@ These are keywords like `else' or `catch'." (defsubst matlab-line-close-paren-outer-point (lvl1) "The poit the outermost parenthetical expression start is at." (nth mlf-paren-outer-point lvl1)) + +;;; LEVEL 2 SCANNER +;; +;; This scanner extracts information that affects the NEXT line of ML code. +;; This inludes things like ellipsis and keyword chains like if/end blocks. +;; +;; Level 2 scanning information cascades from line-to-line, several fields will +;; be blank unless a previous line is also scanned. +(defconst mlf-level1 0) +(defconst mlf-previous-level1 1) +(defconst mlf-comment 2) +(defconst mlf-comment-col 3) +(defconst mlf-comment-begin 4) +(defun matlab-compute-line-context-lvl-2 (&optional lvl1 previous2 full) + "Compute the level 2 context for the current line of MATLAB code. +Level 2 context are things that are derived from previous lines of code. + +Some of that context is derived from the LVL1 context such as paren depth, +and some scaning previous lines of code. + +LVL1 will be computed if not provided. + +This function will generate a mostly empty structure, and will +fill in context from the PREVIOUS2 input as needed. Empty stats +will be computed by accessors on an as needed basis. If PREVIOUS +2 is not provided, it will go back 1 line, scan it for lvl1 data +and use that. + +If the 3rd argument FULL is non-nil, it will take the time to search backward +for all the information it can. + +The returned LVL2 structure will fill out to be a chain of all previous +LVL2 outputs up to a context break. The chains will be summarized in slots +in the returned list for quick access." + (when (not lvl1) (setq lvl1 (matlab-compute-line-context-lvl-1))) + ;; matlab-ltype-help-comm + ;; block start + ;; block end + ;; change in # blocks on this line. + + (save-excursion + (let ((prev-lvl1 (if previous2 (car previous2) + ;; Not provided, go back 1 and get lvl1 data. + (save-excursion) + (beginning-of-line) + (when (not (bobp)) + (forward-char -1) + (matlab-compute-line-context 1)))) + (comment nil) + (comment-col 0) + (comment-begin nil) + (tmp nil) + ) + + ;; COMMENT DATA + (cond + ;; If we are in a block comment, no other needs. + ((setq tmp (matlab-line-block-comment-start lvl1)) + (setq comment 'comment-block + comment-col (save-excursion (goto-char tmp) + (current-column)))) + ;; If prev line has a comment, fill in. + ((setq tmp (matlab-line-end-comment-column prev-lvl1)) + (setq comment 'comment + comment-col tmp) + (when (matlab-line-comment-p prev-lvl1) + (if (and previous2 ) + (setq comment-begin (nth mlf-comment-begin previous2)) + ;; If not provided, compute. + (matlab-with-context-line prev-lvl1 + (forward-comment -100000) + (matlab-compute-line-context 1))))) + ) + + + + + (list lvl1 prev-lvl1 comment comment-col comment-begin + + )))) ;;; Scanning Accessor utilities ;; @@ -451,66 +553,76 @@ Make sure the cache doesn't exceed max size." "Describe the indentation context for the current line." (interactive) (back-to-indentation) - (let* ((lvl1 (matlab-compute-line-context 1)) - (paren-inner-char (nth mlf-paren-inner-char lvl1)) - (open (format "%c" (or paren-inner-char ?\())) - (close (format "%c" - (cond ((not paren-inner-char) ?\)) - ((= paren-inner-char ?\() ?\)) - ((= paren-inner-char ?\[) ?\]) - ((= paren-inner-char ?\{) ?\}) - (t ??)))) - (innerparenstr (format "%s%d%s" open (nth mlf-paren-depth lvl1) close)) - (outerp-char (nth mlf-paren-outer-char lvl1)) - (outerp-open (if outerp-char (format "%c" outerp-char) "")) - (outerp-close (if (not outerp-char) "" - (format "%c" - (cond ((= outerp-char ?\() ?\)) - ((= outerp-char ?\[) ?\]) - ((= outerp-char ?\{) ?\}) - (t ??))))) - (outerparenopen "") - (outerparenclose "") - (extraopen "") - (extraclose "") - ) - (cond ((= (nth mlf-paren-depth lvl1) 0) - ;; 0 means no parens - so shade out parens to indicate. - (setq open (propertize open 'face 'shadow) - close (propertize close 'face 'shadow))) - ((<= (nth mlf-paren-depth lvl1) 1) - ;; If 1 or fewer parens, clear out outer chars - (setq outerp-open "" - outerp-close "")) - ((> (nth mlf-paren-depth lvl1) 2) - ;; If more than 2, signal more unknown parens in between - (setq outerp-open (concat outerp-open (string (decode-char 'ucs #x2026))) - outerp-close (concat (string (decode-char 'ucs #x2026)) outerp-close)))) - (if (< (nth mlf-paren-delta lvl1) 0) - (setq extraopen (format "<%d" (abs (nth mlf-paren-delta lvl1)))) - (when (> (nth mlf-paren-delta lvl1) 0) - (setq extraclose (format "%d>" (nth mlf-paren-delta lvl1))))) + (let* ((MSG1 "") + (MSG2 "") + (lvl1 (matlab-compute-line-context 1)) + (lvl2 (matlab-compute-line-context 2))) + (let* ((paren-inner-char (nth mlf-paren-inner-char lvl1)) + (open (format "%c" (or paren-inner-char ?\())) + (close (format "%c" + (cond ((not paren-inner-char) ?\)) + ((= paren-inner-char ?\() ?\)) + ((= paren-inner-char ?\[) ?\]) + ((= paren-inner-char ?\{) ?\}) + (t ??)))) + (innerparenstr (format "%s%d%s" open (nth mlf-paren-depth lvl1) close)) + (outerp-char (nth mlf-paren-outer-char lvl1)) + (outerp-open (if outerp-char (format "%c" outerp-char) "")) + (outerp-close (if (not outerp-char) "" + (format "%c" + (cond ((= outerp-char ?\() ?\)) + ((= outerp-char ?\[) ?\]) + ((= outerp-char ?\{) ?\}) + (t ??))))) + (outerparenopen "") + (outerparenclose "") + (extraopen "") + (extraclose "") + ) + (cond ((= (nth mlf-paren-depth lvl1) 0) + ;; 0 means no parens - so shade out parens to indicate. + (setq open (propertize open 'face 'shadow) + close (propertize close 'face 'shadow))) + ((<= (nth mlf-paren-depth lvl1) 1) + ;; If 1 or fewer parens, clear out outer chars + (setq outerp-open "" + outerp-close "")) + ((> (nth mlf-paren-depth lvl1) 2) + ;; If more than 2, signal more unknown parens in between + (setq outerp-open (concat outerp-open (string (decode-char 'ucs #x2026))) + outerp-close (concat (string (decode-char 'ucs #x2026)) outerp-close)))) + (if (< (nth mlf-paren-delta lvl1) 0) + (setq extraopen (format "<%d" (abs (nth mlf-paren-delta lvl1)))) + (when (> (nth mlf-paren-delta lvl1) 0) + (setq extraclose (format "%d>" (nth mlf-paren-delta lvl1))))) - - (message "%s%s >>%d %s%s%s%s%s %s %s" (nth mlf-ltype lvl1) - (format " %s" (or (nth mlf-stype lvl1) "")) - (nth mlf-point lvl1) - ;; paren system - extraopen - outerp-open - innerparenstr - outerp-close - extraclose - (cond ((eq (nth mlf-end-comment-type lvl1) 'comment) "%") - ((eq (nth mlf-end-comment-type lvl1) 'ellipsis) "...") - (t "")) - (if (matlab-line-end-comment-column lvl1) - (format " %d" (matlab-line-end-comment-column lvl1)) - "") - ) - ) - ) + (setq MSG1 + (format "%s%s >>%d %s%s%s%s%s %s %s" (nth mlf-ltype lvl1) + (format " %s" (or (nth mlf-stype lvl1) "")) + (nth mlf-indent lvl1) + ;; paren system + extraopen + outerp-open + innerparenstr + outerp-close + extraclose + (cond ((eq (nth mlf-end-comment-type lvl1) 'comment) "%") + ((eq (nth mlf-end-comment-type lvl1) 'ellipsis) "...") + (t "")) + (if (matlab-line-end-comment-column lvl1) + (format " %d" (matlab-line-end-comment-column lvl1)) + "") + )) + ) + (let* ((lvl2 (matlab-compute-line-context-lvl-2 lvl1)) + ;;(comment + ) + + ) + + (message "%s" (concat MSG1 MSG2)) + )) (provide 'matlab-scan) From 5413e93efa820f63a641e442bca903df9d68ba41 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 13:21:01 -0500 Subject: [PATCH 056/168] matlab.el: (matlab-last-script-type-guess): New cache. (matlab-last-guess-decl-p): New fcn, non-nil if fcn or class. (matlab-guess-script-type): Cache result. (matlab-beginning-of-command): Don't ask about help txt if not in a function or class. (matlab-ltype-code): Fix bug. (matlab-lattr-block-cont, matlab-lattr-block-close): Short circuit if not on a code line. Don't scan into end line comments. (matlab-calculate-indentation-1) (matlab-next-line-indentation): Don't ask about help text if not in function or class script. --- matlab.el | 188 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 107 insertions(+), 81 deletions(-) diff --git a/matlab.el b/matlab.el index 10eb3ac..db33a8e 100644 --- a/matlab.el +++ b/matlab.el @@ -182,24 +182,31 @@ If the value is 'guess, then we guess if a file has end when ) (defvar matlab-defun-regex) ;; Quiet compiler warning (is defined below) +(defvar matlab-last-script-type-guess nil + "The last time we guessed the script type, what was it?") +(defun matlab-last-guess-decl-p () + "Return non-nil if our last guess at a script type was function or class." + (memq matlab-last-script-type-guess '(function class))) + (defun matlab-guess-script-type () "Guess the type of script this `matlab-mode' file contains. Returns one of 'empty, 'script, 'function, 'class." - (save-excursion - (goto-char (point-min)) - (if (matlab-find-code-line) - ;; We found some code, what is it? - (if (looking-at matlab-defun-regex) - ;; A match - figure out the type of thing. - (let ((str (match-string-no-properties 1))) - (cond ((string= str "function") - 'function) - ((string= str "classdef") - 'class))) - ;; No function or class - just a script. - 'script) - ;; No lines of code, we are empty, so undecided. - 'empty))) + (setq matlab-last-script-type-guess + (save-excursion + (goto-char (point-min)) + (if (matlab-find-code-line) + ;; We found some code, what is it? + (if (looking-at matlab-defun-regex) + ;; A match - figure out the type of thing. + (let ((str (match-string-no-properties 1))) + (cond ((string= str "function") + 'function) + ((string= str "classdef") + 'class))) + ;; No function or class - just a script. + 'script) + ;; No lines of code, we are empty, so undecided. + 'empty)))) (defun matlab-do-functions-have-end-p (&optional no-navigate) "Look at the contents of the current buffer and decide if functions have end. @@ -2093,7 +2100,8 @@ Travels across continuations." ;; Using forward-comment is very fast, and just skipps all comments until ;; we hit a line of code. ;; NOTE: This may fail with poorly indented code. - (when (or (matlab-scan-comment-help-p lvl1) ;(matlab-ltype-help-comm) + (when (or (and (matlab-last-guess-decl-p) + (matlab-scan-comment-help-p lvl1)) ;(matlab-ltype-help-comm) (matlab-ltype-continued-comm)) (forward-comment -100000)) @@ -2201,7 +2209,8 @@ Return the symbol 'blockcomm if we are in a block comment." (defun matlab-ltype-code () ; line of code "Return t if current line is a MATLAB code line." - (matlab-line-code-p (matlab-compute-line-context 1))) + (let ((lvl (matlab-compute-line-context 1))) + (not (or (matlab-line-comment-p lvl) (matlab-line-empty-p lvl))))) ;(and (not (matlab-ltype-empty)) (not (matlab-ltype-comm)))) (defun matlab-lattr-comm () ; line has comment @@ -2279,30 +2288,36 @@ by close, the first character is the end of an array." "Return a number representing the number of unterminated block constructs. This is any block, such as if or for, that doesn't have an END on this line. Optional EOL indicates a virtual end of line." - (let ((v 0)) - (save-excursion - (beginning-of-line) - (save-restriction - (narrow-to-region (point) (or eol (matlab-point-at-eol))) - (matlab-navigation-syntax - (while (re-search-forward (concat "\\<" (matlab-block-beg-re) "\\>") - nil t) - (if (or (matlab-cursor-in-string-or-comment) - (not (save-excursion (forward-word -1) - (matlab-cursor-on-valid-block-start)))) - ;; Do nothing if in comment, or if the thing we skipped over was - ;; an invalid block construct (based on local context) - nil - ;; Increment counter, move to end. - (setq v (1+ v)) - (let ((p (point))) - (forward-word -1) - (condition-case nil - (progn - (matlab-forward-sexp) - (setq v (1- v))) - (error (goto-char p)))))) - v))))) + (let ((v 0) + (lvl (matlab-compute-line-context 1))) + (if (or (matlab-line-comment-p lvl) + (matlab-line-empty-p lvl)) + ;; If this line is comments or empty, no code to scan + 0 + (save-excursion + (beginning-of-line) + (save-restriction + (narrow-to-region (point) (or eol (save-excursion (matlab-line-end-of-code lvl) + (point)))) + (matlab-navigation-syntax + (while (re-search-forward (concat "\\<" (matlab-block-beg-re) "\\>") + nil t) + (if (or (matlab-cursor-in-string-or-comment) + (not (save-excursion (forward-word -1) + (matlab-cursor-on-valid-block-start)))) + ;; Do nothing if in comment, or if the thing we skipped over was + ;; an invalid block construct (based on local context) + nil + ;; Increment counter, move to end. + (setq v (1+ v)) + (let ((p (point))) + (forward-word -1) + (condition-case nil + (progn + (matlab-forward-sexp) + (setq v (1- v))) + (error (goto-char p)))))) + v)))))) (defun matlab-lattr-middle-block-cont () "Return the number of middle block continuations. @@ -2334,47 +2349,56 @@ special items." (defun matlab-lattr-block-close (&optional start) "Return the number of closing block constructs. Argument START is where to start searching from." - (let ((v 0)) + (let ((v 0) + (lvl1 (matlab-compute-line-context 1))) (save-excursion (when start (goto-char start)) - (matlab-with-current-command - (goto-char (point-at-eol)) + (if (matlab-line-comment-p lvl1) + ;; If this is even vagely a comment line, then there is no + ;; need to do any scanning. + 0 + ;; Else, lets scan. + (matlab-with-current-command + ;; We used to do this, but now... + ;;(goto-char (point-at-eol)) + ;; lets only scan from the beginning of the comment + (matlab-line-end-of-code lvl1) - ;; If in a comment, move out of it first. - (when (matlab-beginning-of-string-or-comment) - ;; in case of no space between comment and end, need to move back - ;; over the comment chart for next search to work. - ;;(forward-char 1) - ) + ;; If in a comment, move out of it first. + (when (matlab-beginning-of-string-or-comment) + ;; in case of no space between comment and end, need to move back + ;; over the comment char for next search to work. + ;;(forward-char 1) + ) - ;; Count every END in the line, skipping over active blocks - (while (re-search-backward (concat "\\<" (matlab-block-end-re) "\\>") - nil t) - (let ((startmove (match-end 0)) - (nomove (point))) - (cond - ((matlab-beginning-of-string-or-comment) - ;; Above returns non-nil if it was in a string or comment. - ;; In that case, we need to keep going. - nil) - ((not (matlab-valid-end-construct-p)) - ;; Not a valid end, just move past it. - (goto-char nomove)) - (t - ;; Lets count these end constructs. - (setq v (1+ v)) - (if (matlab-backward-sexp t t) - (setq v (1- v)) - (goto-char nomove))) - ))) - ;; If we can't scoot back, do a cheat-test to see if there - ;; is a matching else or elseif. - (goto-char (point-min)) - (back-to-indentation) - (if (looking-at (matlab-block-mid-re)) - (setq v (1- v))) - ;; Return nil, or a number - (if (<= v 0) nil v))))) + ;; Count every END in the line, skipping over active blocks + (while (re-search-backward (concat "\\<" (matlab-block-end-re) "\\>") + nil t) + (let ((startmove (match-end 0)) + (nomove (point))) + (cond + ((matlab-beginning-of-string-or-comment) + ;; Above returns non-nil if it was in a string or comment. + ;; In that case, we need to keep going. + nil) + ((not (matlab-valid-end-construct-p)) + ;; Not a valid end, just move past it. + (goto-char nomove)) + (t + ;; Lets count these end constructs. + (setq v (1+ v)) + (if (matlab-backward-sexp t t) + (setq v (1- v)) + (goto-char nomove))) + ))) + ;; If we can't scoot back, do a cheat-test to see if there + ;; is a matching else or elseif. + (goto-char (point-min)) + (back-to-indentation) + (if (looking-at (matlab-block-mid-re)) + (setq v (1- v))) + ;; Return nil, or a number + (if (<= v 0) nil v)))))) (defun matlab-lattr-local-end () "Return t if this line begins with an end construct." @@ -2499,7 +2523,8 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio ((eq comment-style 'block-body) (list 'comment (+ 2 (matlab-line-end-comment-column lvl1)))) ;; HELP COMMENT and COMMENT REGION - ((setq tmp (matlab-scan-comment-help-p lvl1)) + ((and (matlab-last-guess-decl-p) + (setq tmp (matlab-scan-comment-help-p lvl1))) (list 'comment-help tmp)) ;; COMMENT REGION comments ((matlab-line-comment-ignore-p lvl1) @@ -2748,12 +2773,13 @@ See `matlab-calculate-indentation'." (goto-char (point-min)) (back-to-indentation) (setq lvl1 (matlab-compute-line-context 1)) - (let ((cc (or (matlab-lattr-block-close startpnt) 0)) + (let ((cc (or (matlab-lattr-block-close startpnt) 0)) (end (matlab-line-end-p lvl1)) ;(matlab-lattr-local-end)) (bc (matlab-lattr-block-cont startpnt)) (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) - (hc (and (matlab-indent-function-body-p) + (hc (and (matlab-last-guess-decl-p) + (matlab-indent-function-body-p) (matlab-scan-comment-help-p lvl1))) ;(matlab-ltype-help-comm))) (rc (and (/= 0 matlab-comment-anti-indent) (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) From 7321ab5c833e70e39ba371617c5df30d360677e3 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 19:46:05 -0500 Subject: [PATCH 057/168] matlab.el: (matlab-mode): Specify indent-region-function. (matlab-lattr-array-cont): Accept lvl2 input and use it when avail. (matlab-indent-region): New. Computes a lvl2 context and maintains it line-to-line. (matlab-indent-line): Compute a lvl2 context. Move original contents to matlab--indent-line. (matlab--indent-line): New per-line indenter. Takes lvl2 context and passes it through. (matlab-calculate-indentation): Accept lvl2 context, pass through. (matlab-calculate-indentation-1): Accept lvl2 context. Derive lvl1 context from lvl2. (matlab-next-line-indentation): Accept lvl2 context, and use it. (matlab-comment-indent): Use renamed matlab--calc-indent. (matlab-show-line-info): compute lvl2 and use it with above changed apis matlab-scan.el: (matlab-line-point, matlab-line-indentation): New (mlf-* for lvl-2 context): Rename for consistency with what they do. (matlab-compute-linecontext-lvl-2): Revampe computing of lvl1 (if not passed in), internal names. Delete old comment stuff (wasn't used.) Implement cascading lvl2 maintenance from lvl1-prev, or bobp as nil. (matlab-line-in-array): New helper. (matlab-scan-cache-get): Log hits to stats. (matlab-scan-cache-stats): New var (matlab-scan-stat-reset): New cmd (matlab-scan-stat-inc): New (matlab-scan-stats-print): New --- matlab-scan.el | 199 ++++++++++++++++++++++++++++++++++++++++--------- matlab.el | 113 ++++++++++++++++++++-------- 2 files changed, 242 insertions(+), 70 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 41bff83..8b05137 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -245,6 +245,14 @@ Returns the value from the last part of forms." (goto-char (nth mlf-point (or (car-safe (car ,__context)) ,__context))) ,@forms)) +(defsubst matlab-line-point (lvl1) + "Return the point at beginning of indentation for line specified by lvl1." + (nth mlf-point lvl1)) + +(defsubst matlab-line-indentation (lvl1) + "Return the indentation of the line specified by LVL1." + (nth mlf-indent lvl1)) + (defsubst matlab-line-empty-p (lvl1) "Return t if the current line is empty based on LVL1 cache." (eq (car lvl1) 'empty)) @@ -360,10 +368,11 @@ If the line starts with a comment return nil, otherwise t." ;; Level 2 scanning information cascades from line-to-line, several fields will ;; be blank unless a previous line is also scanned. (defconst mlf-level1 0) -(defconst mlf-previous-level1 1) -(defconst mlf-comment 2) -(defconst mlf-comment-col 3) -(defconst mlf-comment-begin 4) +(defconst mlf-previous-line 1) +(defconst mlf-previous-nonempty 2) +(defconst mlf-previous-code 3) +(defconst mlf-previous-block 4) +(defconst mlf-previous-fcn 5) (defun matlab-compute-line-context-lvl-2 (&optional lvl1 previous2 full) "Compute the level 2 context for the current line of MATLAB code. @@ -380,6 +389,9 @@ will be computed by accessors on an as needed basis. If PREVIOUS 2 is not provided, it will go back 1 line, scan it for lvl1 data and use that. +Empty stats are filled in as t. nil means there is no context, or +a lvl1 stat block for the line with that meaning. + If the 3rd argument FULL is non-nil, it will take the time to search backward for all the information it can. @@ -393,46 +405,105 @@ in the returned list for quick access." ;; change in # blocks on this line. (save-excursion - (let ((prev-lvl1 (if previous2 (car previous2) - ;; Not provided, go back 1 and get lvl1 data. - (save-excursion) - (beginning-of-line) - (when (not (bobp)) - (forward-char -1) - (matlab-compute-line-context 1)))) - (comment nil) - (comment-col 0) - (comment-begin nil) + (let ((prev-lvl1 t) + (prev-nonempty t) + (prev-code1 t) + (prev-block1 t) + (prev-fcn1 t) (tmp nil) ) - ;; COMMENT DATA - (cond - ;; If we are in a block comment, no other needs. - ((setq tmp (matlab-line-block-comment-start lvl1)) - (setq comment 'comment-block - comment-col (save-excursion (goto-char tmp) - (current-column)))) - ;; If prev line has a comment, fill in. - ((setq tmp (matlab-line-end-comment-column prev-lvl1)) - (setq comment 'comment - comment-col tmp) - (when (matlab-line-comment-p prev-lvl1) - (if (and previous2 ) - (setq comment-begin (nth mlf-comment-begin previous2)) - ;; If not provided, compute. - (matlab-with-context-line prev-lvl1 - (forward-comment -100000) - (matlab-compute-line-context 1))))) - ) + ;; copy data from previous2. + (if previous2 + (setq prev-lvl1 (car previous2) + prev-nonempty (nth mlf-previous-nonempty previous2) + prev-code1 (nth mlf-previous-code previous2) + prev-block1 (nth mlf-previous-block previous2) + prev-fcn1 (nth mlf-previous-fcn previous2) + ) + ;; Else - this is the one thing we'll compute. + (save-excursion + (beginning-of-line) + (if (bobp) + (setq prev-lvl1 nil) + (forward-char -1) + (setq prev-lvl1 (matlab-compute-line-context 1))) + )) - + ;; prev line can be nil if at beginning of buffer. + (if (not prev-lvl1) + (setq prev-nonempty nil + prev-code1 nil + prev-block1 nil + prev-fcn1 nil + ) + + ;; If we do have a previous lvl1, then we can compute from it. + ;; Override parts of our data based on prev-lvl1 which might + ;; be one of the things we care about. + (when (not (matlab-line-empty-p prev-lvl1)) + ;; Our prev line was non empty, so remember. + (setq prev-nonempty prev-lvl1) + + (if (not (memq (car prev-lvl1) '(empty comment))) + (progn + ;; Our prev line wasn't a comment or empty, so remember. + (setq prev-code1 prev-lvl1) + (when (eq (car prev-lvl1) 'block-start) + ;; We have a block start, so remember + (setq prev-block1 prev-lvl1) - (list lvl1 prev-lvl1 comment comment-col comment-begin + (when (eq (nth mlf-stype prev-lvl1) 'decl) + (setq prev-fcn1 prev-lvl1)) + )) + + ;; Do something with comment here ?? + ))) + + (list lvl1 prev-lvl1 prev-nonempty prev-code1 prev-block1 prev-fcn1 )))) +;;; Simple Accessors +;; +(defun matlab-get-lvl1-from-lvl2 (lvl2) + "Return a LVL1 context. +If input LVL2 is a level 2 context, return the lvl1 from it. +If the input is a lvl1, then return that. +If LVL2 is nil, compute it." + (if lvl2 + (if (consp (car lvl2)) (car lvl2) lvl2) + (matlab-compute-line-context 1))) + +(defun matlab-previous-line (lvl2) + "Return the previous line from lvl2 context." + (matlab-scan-stat-inc 'prevline) + (nth mlf-previous-line lvl2)) + +(defun matlab-previous-nonempty-line (lvl2) + "Return lvl1 ctxt for previous non-empty line." + (let ((prev (nth mlf-previous-nonempty lvl2)) + ) + (if (eq prev t) + ;; Compute it and stash it. + (save-excursion + (matlab-scan-stat-inc 'nonemptymiss) + (beginning-of-line) + (skip-syntax-backward " >") ;; skip spaces, and newlines w/ comment end on it. + (setq prev (matlab-compute-line-context 1)) + (setcar (nthcdr mlf-previous-nonempty lvl2) prev)) + ;; else record a cache hit + (matlab-scan-stat-inc 'nonempty) + ) + prev)) + +;;; MATLAB focused queries (more specific names than for first set) +;; +(defun matlab-line-in-array (lvl2) + "Return the location of an opening paren if in array parens." + (matlab-line-close-paren-outer-point (matlab-get-lvl1-from-lvl2 lvl2))) + ;;; Scanning Accessor utilities ;; ;; Some utilities require some level of buffer scanning to get the answer. @@ -518,8 +589,12 @@ Since the list isn't sorted, not optimizations possible.") (while (and cache (/= pt (nth mlf-point (car cache)))) (setq cache (cdr cache))) ;; If we found a match, return it. - (when (and cache (= pt (nth mlf-point (car cache)))) - (car cache)))) + (if (and cache (= pt (nth mlf-point (car cache)))) + (progn + (matlab-scan-stat-inc 'lvl1) + (car cache)) + (matlab-scan-stat-inc 'lvl1-miss) + nil))) (defun matlab-scan-cache-put (ctxt) "Put a context onto the cache. @@ -549,6 +624,56 @@ Make sure the cache doesn't exceed max size." ;;; Debugging and Querying ;; +(defvar matlab-scan-cache-stats nil + "Cache stats for tracking effectiveness of the cache.") +(defun matlab-scan-stat-reset (&optional arg) + "Reset the stats cache." + (interactive "P") + (if arg + (progn (setq matlab-scan-cache-stats nil) + (message "Disable matlab scanner stats gathering.")) + (message "Emable matlab scanner stats gathering.") + (setq matlab-scan-cache-stats (obarray-make 13)))) + +(defun matlab-scan-stat-inc (thing) + "Increment the stat associated with thing." + (when matlab-scan-cache-stats + (let ((sym (intern-soft (symbol-name thing) matlab-scan-cache-stats))) + (when (not sym) + (set (setq sym (intern (symbol-name thing) matlab-scan-cache-stats)) 0)) + (set sym (1+ (symbol-value sym)))) + (matlab-scan-stats-print 'summary))) + +(defun matlab-scan-stats-print (&optional summary) + "Display stats for scanner hits." + (interactive "P") + (let ((res nil)) + (obarray-map (lambda (sym) + (push (cons (symbol-name sym) (symbol-value sym)) res)) + matlab-scan-cache-stats) + (setq res (sort res (lambda (a b) (string< (car a) (car b))))) + (if summary + (when (not noninteractive) + ;; show a short form. + (message (mapconcat (lambda (pair) + (concat (car pair) ":" (format "%d" (cdr pair)))) + res " "))) + ;; Else, show a long form. + (let ((printfcn (lambda (pair) + (princ (concat (format "%-8s" (concat (car pair) ":")) "\t" + (format "%d" (cdr pair)) "\n"))))) + (if noninteractive + (progn + ;; Print to stdout when in batch mode. + (princ "\nCache Key\tHits\n") + (mapc printfcn res) + (princ "---\n")) + ;; Display in a buffer + (with-output-to-temp-buffer "*MATLAB SCANNER STATS*" + (princ "Cache Key\tHits\n----------\t------\n") + (mapc printfcn res))) + )))) + (defun matlab-describe-line-indent-context () "Describe the indentation context for the current line." (interactive) diff --git a/matlab.el b/matlab.el index db33a8e..b53fb24 100644 --- a/matlab.el +++ b/matlab.el @@ -1344,6 +1344,8 @@ All Key Bindings: (setq indent-tabs-mode nil) (make-local-variable 'indent-line-function) (setq indent-line-function 'matlab-indent-line) + (make-local-variable 'indent-region-function) + (setq indent-region-function 'matlab-indent-region) (make-local-variable 'comment-column) (setq comment-column matlab-comment-column) (make-local-variable 'comment-indent-function) @@ -2266,10 +2268,10 @@ based on what it ends with." (save-excursion (and (if (= -1 (forward-line -1)) nil t) (matlab-lattr-cont)))) -(defun matlab-lattr-array-cont () +(defun matlab-lattr-array-cont (&optional lvl2) "Return non-nil if current line is in an array. If the entirety of the array is on this line, return nil." - (matlab-line-close-paren-outer-point (matlab-compute-line-context 1))) + (matlab-line-close-paren-outer-point (matlab-get-lvl1-from-lvl2 lvl2))) ;; (condition-case nil ;; (save-excursion ;; (beginning-of-line) @@ -2437,40 +2439,80 @@ If there isn't one, then return nil, point otherwise." ;;; Indent functions ========================================================== +(defun matlab-indent-region (start end) + "Indent the region between START And END for MATLAB mode. +Unlike `indent-region-line-by-line', this function captures +parsing state and re-uses that state along the way." + (interactive) + (matlab-navigation-syntax + (save-excursion + (setq end (copy-marker end)) + (goto-char start) + (let ((pr (unless (minibufferp) + (make-progress-reporter "MATLAB Indenting region..." (point) end))) + (lvl2 nil) + (lvl1 nil) + ) + (while (< (point) end) + (unless (and (bolp) (eolp)) + ;; This is where we indent each line + (setq lvl1 (matlab-compute-line-context 1) + lvl2 (matlab-compute-line-context 2 lvl1 lvl2)) + (matlab--indent-line lvl2) + ) + (forward-line 1) + (and pr (progress-reporter-update pr (point)))) + (and pr (progress-reporter-done pr)) + (move-marker end nil))))) + + (defun matlab-indent-line () "Indent a line in `matlab-mode'." (interactive) - (let ((i (matlab-calc-indent)) - (ci (current-indentation)) - (cc (current-column))) + (matlab-navigation-syntax + (let ((lvl2 (matlab-compute-line-context 2))) + (matlab--indent-line lvl2)))) + +(defun matlab--indent-line (lvl2) + "Indent the current line according to MATLAB mode. +Input LVL2 is a pre-scanned context from `matlab-compute-line-context' lvl2. +Used internally by `matlab-indent-line', and `matlab-indent-region'." + (let* ((i (matlab--calc-indent lvl2)) + (ci (current-indentation)) + (diff (- ci i)) + (cc (current-column))) (save-excursion (back-to-indentation) - (if (= i (current-column)) - nil - (beginning-of-line) - (delete-horizontal-space) - (indent-to i))) - (if (<= cc ci) (move-to-column (max 0 i))) - )) + (cond ((= diff 0) ;; Already a match - do nothing. + nil) + ((< diff 0) ;; Too short - Add stuff + (indent-to i)) + (t ;; Too much, delete some. + (delete-region (- (point) diff) (point))))) + (if (<= cc ci) (move-to-column (max 0 i))) )) -(defun matlab-calc-indent () +(defun matlab--calc-indent (&optional lvl2) "Return the appropriate indentation for this line as an integer." - (interactive) + ;; In case it wasn't provided. + (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) ;; The first step is to find the current indentation. ;; This is defined to be zero if all previous lines are empty. - (let* ((ci (if (matlab-lattr-array-cont) + (let* ((ci (if (matlab-line-in-array lvl2) ;; If we are inside an array continuation, then we shouldn't ;; need to do anything complicated here b/c we'll just ignore - ;; the returned value in the next step. Return current indentation. - (save-excursion - (matlab-prev-line) - (current-indentation)) + ;; the returned value in the next step. Return current indentation + ;; of the previous non-empty line. + (matlab-line-indentation (matlab-previous-nonempty-line lvl2)) + ;; Else, the next line might recommend an indentation based ;; on it's own context. (save-excursion (if (not (matlab-prev-line)) 0 - (matlab-next-line-indentation))))) - (sem (matlab-calculate-indentation ci))) + (matlab-next-line-indentation lvl2))))) + + ;; Compute this line's indentation based on recommendation of previous + ;; line. + (sem (matlab-calculate-indentation ci lvl2))) ;; simplistic (nth 1 sem))) @@ -2478,7 +2520,7 @@ If there isn't one, then return nil, point otherwise." "This end closes a function definition.\nDo you want functions to have ends? " "Prompt the user about whether to change `matlab-functions-have-end'.") -(defun matlab-calculate-indentation (current-indentation) +(defun matlab-calculate-indentation (current-indentation &optional lvl2) "Calculate out the indentation of the current line. Return a list of descriptions for this line. Return format is: '(TYPE DEPTHNUMBER) @@ -2488,7 +2530,10 @@ this line. Argument CURRENT-INDENTATION is what the previous line thinks this line's indentation should be. See `matlab-next-line-indentation'." (matlab-navigation-syntax - (matlab-calculate-indentation-1 current-indentation))) + ;; TODO - make this compute obsolete. + (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) + ;; Do the compute + (matlab-calculate-indentation-1 current-indentation lvl2))) (defun matlab--maybe-yes-or-no-p (prompt noninteractive-default) "When in non-interactive mode run (yes-or-no-p prompt), @@ -2498,11 +2543,12 @@ otherwise return NONINTERACTIVE-DEFAULT" (yes-or-no-p prompt))) -(defun matlab-calculate-indentation-1 (current-indentation) +(defun matlab-calculate-indentation-1 (current-indentation lvl2) "Do the indentation work of `matlab-calculate-indentation'. -Argument CURRENT-INDENTATION is what the previous line recommends for indentation." +Argument CURRENT-INDENTATION is what the previous line recommends for indentation. +LVL2 is a level 2 scan context with info from previous lines." (let ((ci current-indentation) - (lvl1 (matlab-compute-line-context 1)) + (lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) (blockcomm nil) (tmp nil)) (cond @@ -2755,7 +2801,7 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio cc))))))))) ))) -(defun matlab-next-line-indentation () +(defun matlab-next-line-indentation (lvl2) "Calculate the indentation for lines following this command line. Assume that the following line does not contribute its own indentation \(as it does in the case of nested functions in the following situations): @@ -2764,8 +2810,8 @@ Assume that the following line does not contribute its own indentation not indenting function bodies. See `matlab-calculate-indentation'." (matlab-navigation-syntax - (let ((startpnt (point-at-eol)) - (lvl1 nil) + (let ((lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) + (startpnt (point-at-eol)) ) (save-excursion (matlab-with-current-command @@ -3043,7 +3089,7 @@ Optional argument SOFT indicates that the newline is soft, and not hard." (defun matlab-comment-indent () "Indent a comment line in `matlab-mode'." - (matlab-calc-indent)) + (matlab--calc-indent)) (defun matlab-comment-region (beg-region end-region arg) "Comments every line in the region. @@ -4138,9 +4184,10 @@ desired. Optional argument FAST is not used." (defun matlab-show-line-info () "Display type and attributes of current line. Used in debugging." (interactive) - (let ((msg "line-info:") - (indent (matlab-calculate-indentation (current-indentation))) - (nexti (matlab-next-line-indentation))) + (let* ((msg "line-info:") + (lvl2 (matlab-compute-line-context 2)) + (indent (matlab-calculate-indentation (current-indentation) lvl2)) + (nexti (matlab-next-line-indentation lvl2))) (setq msg (concat msg " Line type: " (symbol-name (car indent)) " This Line: " (int-to-string (nth 1 indent)) From bd55c5a5ecedb28e9a67f20649327f95b7ddd3f7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 20:38:32 -0500 Subject: [PATCH 058/168] matlab-scan.el: (matlab-compute-line-context-lvl-2): Add cache stats capture. (matlab-previous-code-line): New fcn. (matlab-scan-comment-help-p): first arg can now be lvl1 or 2. If lvl2, use it's cached lines to compute. --- matlab-scan.el | 89 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 8b05137..4c6a32e 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -279,6 +279,8 @@ Returns the value from the last part of forms." All lines that start with a comment end with a comment." (when (eq (nth mlf-end-comment-type lvl1) 'comment) (save-excursion + ;; NOTE: if line is in a block comment, this end pt is + ;; really the beginning of the block comment. (goto-char (nth mlf-end-comment-pt lvl1)) (current-column)))) @@ -415,20 +417,24 @@ in the returned list for quick access." ;; copy data from previous2. (if previous2 - (setq prev-lvl1 (car previous2) - prev-nonempty (nth mlf-previous-nonempty previous2) - prev-code1 (nth mlf-previous-code previous2) - prev-block1 (nth mlf-previous-block previous2) - prev-fcn1 (nth mlf-previous-fcn previous2) - ) + (progn + (setq prev-lvl1 (car previous2) + prev-nonempty (nth mlf-previous-nonempty previous2) + prev-code1 (nth mlf-previous-code previous2) + prev-block1 (nth mlf-previous-block previous2) + prev-fcn1 (nth mlf-previous-fcn previous2) + ) + (matlab-scan-stat-inc 'prev2) + ) ;; Else - this is the one thing we'll compute. (save-excursion (beginning-of-line) (if (bobp) (setq prev-lvl1 nil) (forward-char -1) - (setq prev-lvl1 (matlab-compute-line-context 1))) - )) + (setq prev-lvl1 (matlab-compute-line-context 1)) + (matlab-scan-stat-inc 'prev2miss) + ))) ;; prev line can be nil if at beginning of buffer. (if (not prev-lvl1) @@ -491,6 +497,7 @@ If LVL2 is nil, compute it." (matlab-scan-stat-inc 'nonemptymiss) (beginning-of-line) (skip-syntax-backward " >") ;; skip spaces, and newlines w/ comment end on it. + ;; TODO - this stops on ignore comments. (setq prev (matlab-compute-line-context 1)) (setcar (nthcdr mlf-previous-nonempty lvl2) prev)) ;; else record a cache hit @@ -498,6 +505,42 @@ If LVL2 is nil, compute it." ) prev)) +(defun matlab-previous-code-line (lvl2) + "Return lvl1 ctxt for previous non-empty line." + (let ((prev (nth mlf-previous-code lvl2)) + ) + (if (eq prev t) + ;; Compute it and stash it. + (save-excursion + (matlab-scan-stat-inc 'codemiss) + (beginning-of-line) + (forward-comment -100000) ;; Skip over all whitespace and comments. + (setq prev (matlab-compute-line-context 1)) + (setcar (nthcdr mlf-previous-code lvl2) prev)) + ;; else record a cache hit + (matlab-scan-stat-inc 'code) + ) + prev)) + +;;(defun matlab-previous-fcn-line (lvl2) +;; "Return lvl1 ctxt for previous non-empty line." +;; (let ((prev (nth mlf-previous-fcn lvl2)) +;; ) +;; (if (eq prev t) +;; ;; Compute it and stash it. +;; (save-excursion +;; (matlab-scan-stat-inc 'fcnmiss) +;; (beginning-of-line) +;; ;; TODO +;; (setq prev (matlab-compute-line-context 1)) +;; (setcar (nthcdr mlf-previous-fcn lvl2) prev)) +;; ;; else record a cache hit +;; (matlab-scan-stat-inc 'fcn) +;; ) +;; prev)) + + + ;;; MATLAB focused queries (more specific names than for first set) ;; (defun matlab-line-in-array (lvl2) @@ -508,19 +551,31 @@ If LVL2 is nil, compute it." ;; ;; Some utilities require some level of buffer scanning to get the answer. ;; Keep those separate so they can depend on the earlier decls. -(defun matlab-scan-comment-help-p (lvl1 &optional pt) +(defun matlab-scan-comment-help-p (ctxt &optional pt) "Return declaration column if the current line is part of a help comment. +Use the context CTXT as a lvl1 or lvl2 context to compute. Declarations are things like functions and classdefs. Indentation a help comment depends on the column of the declaration. Optional PT, if non-nil, means return the point instead of column" - (and (matlab-line-comment-p lvl1) - (save-excursion - (beginning-of-line) - (forward-comment -100000) - (let ((c-lvl1 (matlab-compute-line-context 1))) - (when (matlab-line-declaration-p c-lvl1) - (if pt (point) (current-indentation)))) - ))) + (let ((lvl2 nil) (lvl1 nil)) + (if (symbolp (car ctxt)) + (setq lvl1 ctxt) + (setq lvl1 (matlab-get-lvl1-from-lvl2 ctxt) + lvl2 ctxt)) + + (when (matlab-line-comment-p lvl1) + ;; Try to get from lvl2 context + (let ((c-lvl1 (when lvl2 (matlab-previous-code-line lvl2)))) + (unless c-lvl1 + ;; If not, compute it ourselves. + (save-excursion + (beginning-of-line) + (forward-comment -100000) + (setq c-lvl1 (matlab-compute-line-context 1)))) + ;; On previous code line - was it a declaration? + (when (matlab-line-declaration-p c-lvl1) + (matlab-with-context-line c-lvl1 + (if pt (point) (current-indentation)))))))) (defun matlab-scan-previous-line-ellipsis-p () "Return the column of the previous line's continuation if there is one. From a5449ab375bc9b5096f7f0e1a33b2de67fc3efc5 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 20:42:53 -0500 Subject: [PATCH 059/168] matlab.el: (matlab-indent-region): Add arg to supress progress indicator. (matlab--change-indentation-override): New feature. (matlab--indent-line): Extract bit that changes buffer to... (matlab--change-indentation): New from above. This fcn is set on the indentation override. (matlab-calculate-indentation-1, matlab-next-line-indentation): Pass lvl2 into help scan. --- matlab.el | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/matlab.el b/matlab.el index b53fb24..2add78e 100644 --- a/matlab.el +++ b/matlab.el @@ -2439,7 +2439,7 @@ If there isn't one, then return nil, point otherwise." ;;; Indent functions ========================================================== -(defun matlab-indent-region (start end) +(defun matlab-indent-region (start end &optional column noprogress) "Indent the region between START And END for MATLAB mode. Unlike `indent-region-line-by-line', this function captures parsing state and re-uses that state along the way." @@ -2448,7 +2448,7 @@ parsing state and re-uses that state along the way." (save-excursion (setq end (copy-marker end)) (goto-char start) - (let ((pr (unless (minibufferp) + (let ((pr (when (and (not (minibufferp)) (not noprogress)) (make-progress-reporter "MATLAB Indenting region..." (point) end))) (lvl2 nil) (lvl1 nil) @@ -2473,23 +2473,34 @@ parsing state and re-uses that state along the way." (let ((lvl2 (matlab-compute-line-context 2))) (matlab--indent-line lvl2)))) +(defvar matlab--change-indentation-override #'matlab--change-indentation + "Tests to override this to validate indent-region.") + (defun matlab--indent-line (lvl2) "Indent the current line according to MATLAB mode. Input LVL2 is a pre-scanned context from `matlab-compute-line-context' lvl2. Used internally by `matlab-indent-line', and `matlab-indent-region'." - (let* ((i (matlab--calc-indent lvl2)) + (let* ((i (matlab--calc-indent lvl2))) + (funcall matlab--change-indentation-override i) + )) + +(defun matlab--change-indentation (new-indentation) + "Change the indentation on line to NEW-INDENTATION. +This function exists so the test harness can override it." + (let* ((i new-indentation) (ci (current-indentation)) - (diff (- ci i)) - (cc (current-column))) + (cc (current-column)) + (diff (- ci i))) (save-excursion (back-to-indentation) (cond ((= diff 0) ;; Already a match - do nothing. nil) ((< diff 0) ;; Too short - Add stuff (indent-to i)) - (t ;; Too much, delete some. + (t ;; Too much, delete some. (delete-region (- (point) diff) (point))))) - (if (<= cc ci) (move-to-column (max 0 i))) )) + (if (<= cc ci) (move-to-column (max 0 i))) + )) (defun matlab--calc-indent (&optional lvl2) "Return the appropriate indentation for this line as an integer." @@ -2549,7 +2560,6 @@ Argument CURRENT-INDENTATION is what the previous line recommends for indentatio LVL2 is a level 2 scan context with info from previous lines." (let ((ci current-indentation) (lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) - (blockcomm nil) (tmp nil)) (cond ;; COMMENTS @@ -2570,7 +2580,7 @@ LVL2 is a level 2 scan context with info from previous lines." (list 'comment (+ 2 (matlab-line-end-comment-column lvl1)))) ;; HELP COMMENT and COMMENT REGION ((and (matlab-last-guess-decl-p) - (setq tmp (matlab-scan-comment-help-p lvl1))) + (setq tmp (matlab-scan-comment-help-p lvl2))) (list 'comment-help tmp)) ;; COMMENT REGION comments ((matlab-line-comment-ignore-p lvl1) @@ -2826,7 +2836,7 @@ See `matlab-calculate-indentation'." (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) (hc (and (matlab-last-guess-decl-p) (matlab-indent-function-body-p) - (matlab-scan-comment-help-p lvl1))) ;(matlab-ltype-help-comm))) + (matlab-scan-comment-help-p lvl2))) ;(matlab-ltype-help-comm))) (rc (and (/= 0 matlab-comment-anti-indent) (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) ;;(not (matlab-ltype-help-comm)) @@ -4214,7 +4224,7 @@ desired. Optional argument FAST is not used." ;; LocalWords: symbolp prev lst nlst nreverse Aki Vehtari backquote ;; LocalWords: defmacro oldsyntax edebug cline ctxt eobp bobp sc fc ;; LocalWords: udir funcall sexps skipnav eolp autoend noerror returnme -;; LocalWords: Unstarted includeelse autostart lattr zerop cellstart blockcomm +;; LocalWords: Unstarted includeelse autostart lattr zerop cellstart ;; LocalWords: linebounds bol commtype startmove nomove charvector sregex ;; LocalWords: insregex laststart bolp calc ci sem DEPTHNUMBER blockstart ;; LocalWords: blockmid blockendless blockend unstarted listp boc parendepth From fd9d7b7088f05163ef8c64a67f07fbe2900367c0 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 20:59:21 -0500 Subject: [PATCH 060/168] matlab-compat.el: (matlab-obarray-make): New compat layer. --- matlab-compat.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/matlab-compat.el b/matlab-compat.el index 05f757c..c035323 100644 --- a/matlab-compat.el +++ b/matlab-compat.el @@ -150,6 +150,11 @@ REGEXP defaults to \"[ \\t\\n\\r]+\"." out)) ) +;; OBARRAYS +(if (fboundp 'obarray-make) + (defalias 'matlab-obarray-make 'obarray-make) + (defun matlab-obarray-make (sz) (make-vector sz 0))) + ;; Finding executables (defun matlab-find-executable-directory (program) "Find the executable PROGRAM on the exec path, following any links. From 3542ef0150db9957bbe0844330540b65640b0d8d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 21:00:24 -0500 Subject: [PATCH 061/168] matlab-scan.el: (make-keyword-table, matlab-scan-stat-reset): Use matlab-obarray-make compat layer. (matlab-scan-stats-print): Use mapatoms instead of obarray-map for compat. --- matlab-scan.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 4c6a32e..a14457f 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -55,7 +55,7 @@ "List of keywords that are part of code blocks.") (defconst matlab-keyword-table - (let ((ans (obarray-make 23))) + (let ((ans (matlab-obarray-make 23))) (mapc (lambda (elt) (set (intern (car elt) ans) (cdr elt))) matlab-block-keyword-list) ans) @@ -688,7 +688,7 @@ Make sure the cache doesn't exceed max size." (progn (setq matlab-scan-cache-stats nil) (message "Disable matlab scanner stats gathering.")) (message "Emable matlab scanner stats gathering.") - (setq matlab-scan-cache-stats (obarray-make 13)))) + (setq matlab-scan-cache-stats (matlab-obarray-make 13)))) (defun matlab-scan-stat-inc (thing) "Increment the stat associated with thing." @@ -703,7 +703,7 @@ Make sure the cache doesn't exceed max size." "Display stats for scanner hits." (interactive "P") (let ((res nil)) - (obarray-map (lambda (sym) + (mapatoms (lambda (sym) (push (cons (symbol-name sym) (symbol-value sym)) res)) matlab-scan-cache-stats) (setq res (sort res (lambda (a b) (string< (car a) (car b))))) From 0fa632d8ae512417c8f49dbf6aa8153c0f059e93 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 13 Mar 2021 21:59:32 -0500 Subject: [PATCH 062/168] matlab.el: (matlab--change-indentation): Protect against trying to indent past beginning of the line. --- matlab.el | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/matlab.el b/matlab.el index 2add78e..8162a4f 100644 --- a/matlab.el +++ b/matlab.el @@ -2497,8 +2497,11 @@ This function exists so the test harness can override it." nil) ((< diff 0) ;; Too short - Add stuff (indent-to i)) - (t ;; Too much, delete some. - (delete-region (- (point) diff) (point))))) + ((< diff ci) ;; Too much, delete some. + (delete-region (- (point) diff) (point))) + (t ;; some sort of bug that wants to delete too much. Ignore. + nil) + )) (if (<= cc ci) (move-to-column (max 0 i))) )) From fb136a19f205c400dad2b2a225b3836e1b558c46 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 00:12:36 -0500 Subject: [PATCH 063/168] tests/metest.el: (matlab-all-syntax-tests): Reset the scanner cache hit stats and print when done. (metest-test-error): Var flag. (mecondition-case-error-msg): When test flag set, forward the error along. (metest-error): Set metest-test-error flag to pass errors through. (metest-indent-counts): New (metest-indents-test): Remove core test into new fcn. Now set matlab--change-indentation-override to our fcn for tracking. Now call matlab-indent-region with flags for testing. (metest-indents-test-hook-fcn): New fcn that does the test on each line from indent-region after indentation is compuated. --- tests/metest.el | 52 ++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/tests/metest.el b/tests/metest.el index 0e0c45d..047c7f7 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -43,6 +43,7 @@ (defun metest-all-syntax-tests () "Run all the syntax tests in this file." (setq debug-on-error t) + (matlab-scan-stat-reset ) ;; Enable scanner statistics logging. (metest-log-init) @@ -54,6 +55,8 @@ (metest-run 'metest-parse-test) (metest-log-report (metest-log-write)) + + (matlab-scan-stats-print) ) (defun metest-run (test) @@ -75,12 +78,14 @@ )) (message ""))) +(defvar metest-test-error nil) (defmacro metest-condition-case-error-msg (&rest forms) "Run FORMS, capturing any errors and associating with (point)." (declare (indent 0) (debug t)) `(condition-case err ,@forms - (error (metest-error "Lisp: %s" (error-message-string err)) + (error (cond ((metest-error (error (car (cdr err))))) + (t (metest-error "Lisp: %s" (error-message-string err)))) 0) )) @@ -237,27 +242,33 @@ "List of files for running syntactic indentation tests.") (defvar metest-indents-test (cons "indenting" met-indents-files)) +(defvar metest-indent-counts 0) (defun metest-indents-test (F) "Run a test to make sure high level block navigation works." - (let ((buf (metest-find-file F)) - (cnt 0)) - (with-current-buffer buf - (goto-char (point-min)) - ;; (indent-region (point-min) (point-max)) - ;;(message ">> Starting indents loop in %S" (current-buffer)) - (while (re-search-forward "!!\\([0-9]+\\)" nil t) - (let* ((num (string-to-number (match-string 1))) - (calc (metest-condition-case-error-msg - (matlab-calc-indent))) - (begin nil)) - (when (not (eq num calc)) - (metest-error "Indentation found is %s, expected %s" - calc num)) - ) - (end-of-line) - (setq cnt (1+ cnt)))) - (kill-buffer buf) - (list cnt "tests"))) + (with-current-buffer (metest-find-file F) + (goto-char (point-min)) + (let ((metest-indent-counts 0) + (matlab--change-indentation-override #'metest-indents-test-hook-fcn)) + (metest-condition-case-error-msg + (matlab-indent-region (point-min) (point-max) nil t)) + (kill-buffer (current-buffer)) + (list metest-indent-counts "tests")))) + +(defun metest-indents-test-hook-fcn (indent) + "Hook fcn used to capture indents from `indent-region'." + (save-excursion + (beginning-of-line) + + (when (re-search-forward "!!\\([0-9]+\\)" (point-at-eol) t) + (let ((num (string-to-number (match-string 1)))) + (setq metest-indent-counts (1+ metest-indent-counts)) + (when (not (eq num indent)) + (metest-error "Indentation computed is %s, expected %s" + indent num)))) + + ;; Now do the indent in case a bad indent will trigger a bug later. + (matlab--change-indentation indent) + )) (defvar met-parser-files '("mpclass.m") "List of files for running semantic parsing tests.") @@ -323,6 +334,7 @@ Do error checking to provide easier debugging." (file-name-nondirectory (buffer-file-name)) (line-number-at-pos))) (post (apply 'format args))) + (setq metest-test-error t) (error (concat pre post)))) ;;; Logging prormance data for the tests From 24059939dd79cca68e6de60e43495dc1de1edb34 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 00:21:33 -0500 Subject: [PATCH 064/168] matlab.el: (matlab--indent-line): refresh the context if the line was indented. (matlab--change-indentation): Fix bug when diff==0 (matlab--calc-indent): Revamp how we compute stuff before matlab-next-line-indentation. (matlab-next-line-indentation): Use more of the contexts passed in. Removed `matlab-with-current-command' use. Replaced with direct call. This shows we no longer need to narrow the region. Remove help comment computation - it seemed unused. (matlab-show-line-info): Update for changed apis. matlab-scan.el: (matlab-with-context-line): Fix bug computing pt to move to. Setup edebug to debug into this macro. (mlf-previous-line1 & 2): Renamed & new slot. (mlf-previous-command-beginning): New slot (matlab-compute-line-context-lvl-2): If avail, store a lvl2 context for the previous line too. Transfer the previous command into new lvl2 context. Add stats logging. (matlab-refresh-line-context-lvl2): New hook to refresh after an indent. (matlab-previous-line-lvl2): New accessor that fills in if empty. (matlab-previous-code-line): Protect against bobp. (matlab-previous-command-begin): New accessor that fills in if empty. --- matlab-scan.el | 133 ++++++++++++++++++++++++++++++++++++------------- matlab.el | 66 +++++++++++++++--------- 2 files changed, 141 insertions(+), 58 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index a14457f..fbe4fba 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -238,11 +238,13 @@ in a single call using fastest methods." "Save excursion, and move point to the line specified by CONTEXT. Takes a lvl1 or lvl2 context. Returns the value from the last part of forms." - (declare (indent 1)) + (declare (indent 1) (debug (form &rest form))) `(save-excursion ;; The CAR of a LVL2 is a LVL1. If __context is LVL1, then ;; the car-safe will return nil - (goto-char (nth mlf-point (or (car-safe (car ,__context)) ,__context))) + (goto-char (nth mlf-point (if (consp (car ,__context)) + (car ,__context) + ,__context))) ,@forms)) (defsubst matlab-line-point (lvl1) @@ -370,13 +372,15 @@ If the line starts with a comment return nil, otherwise t." ;; Level 2 scanning information cascades from line-to-line, several fields will ;; be blank unless a previous line is also scanned. (defconst mlf-level1 0) -(defconst mlf-previous-line 1) -(defconst mlf-previous-nonempty 2) -(defconst mlf-previous-code 3) -(defconst mlf-previous-block 4) -(defconst mlf-previous-fcn 5) +(defconst mlf-previous-line1 1) +(defconst mlf-previous-line2 2) +(defconst mlf-previous-command-beginning 3) +(defconst mlf-previous-nonempty 4) +(defconst mlf-previous-code 5) +(defconst mlf-previous-block 6) +(defconst mlf-previous-fcn 7) -(defun matlab-compute-line-context-lvl-2 (&optional lvl1 previous2 full) +(defun matlab-compute-line-context-lvl-2 (&optional lvl1 previous2) "Compute the level 2 context for the current line of MATLAB code. Level 2 context are things that are derived from previous lines of code. @@ -394,9 +398,6 @@ and use that. Empty stats are filled in as t. nil means there is no context, or a lvl1 stat block for the line with that meaning. -If the 3rd argument FULL is non-nil, it will take the time to search backward -for all the information it can. - The returned LVL2 structure will fill out to be a chain of all previous LVL2 outputs up to a context break. The chains will be summarized in slots in the returned list for quick access." @@ -408,6 +409,8 @@ in the returned list for quick access." (save-excursion (let ((prev-lvl1 t) + (prev-lvl2 t) + (prev-cmd-begin t) (prev-nonempty t) (prev-code1 t) (prev-block1 t) @@ -419,6 +422,8 @@ in the returned list for quick access." (if previous2 (progn (setq prev-lvl1 (car previous2) + prev-lvl2 previous2 + prev-cmd-begin (nth mlf-previous-command-beginning previous2) prev-nonempty (nth mlf-previous-nonempty previous2) prev-code1 (nth mlf-previous-code previous2) prev-block1 (nth mlf-previous-block previous2) @@ -426,7 +431,8 @@ in the returned list for quick access." ) (matlab-scan-stat-inc 'prev2) ) - ;; Else - this is the one thing we'll compute. + ;; Else - previous LVL1 is the one thing we'll compute since we need to + ;; init our trackers. (save-excursion (beginning-of-line) (if (bobp) @@ -438,39 +444,64 @@ in the returned list for quick access." ;; prev line can be nil if at beginning of buffer. (if (not prev-lvl1) - (setq prev-nonempty nil + (setq prev-lvl2 nil + prev-cmd-begin nil + prev-nonempty nil prev-code1 nil prev-block1 nil prev-fcn1 nil ) ;; If we do have a previous lvl1, then we can compute from it. - ;; Override parts of our data based on prev-lvl1 which might + ;; Override parts of our data based on prev-lvl2 which might ;; be one of the things we care about. - (when (not (matlab-line-empty-p prev-lvl1)) - ;; Our prev line was non empty, so remember. - (setq prev-nonempty prev-lvl1) + (when (not (matlab-line-empty-p prev-lvl1)) + ;; Our prev line was non empty, so remember. + (setq prev-nonempty prev-lvl1) - (if (not (memq (car prev-lvl1) '(empty comment))) - (progn - ;; Our prev line wasn't a comment or empty, so remember. - (setq prev-code1 prev-lvl1) + (if (not (memq (car prev-lvl1) '(empty comment))) + (progn + ;; Our prev line wasn't a comment or empty, so remember. + (setq prev-code1 prev-lvl1) - (when (eq (car prev-lvl1) 'block-start) - ;; We have a block start, so remember - (setq prev-block1 prev-lvl1) + (when (and (= (matlab-line-paren-depth prev-lvl1) 0) + (not (matlab-line-ellipsis-p prev-lvl1))) + ;; Our previous line some code, but was not in an + ;; array, nor an ellipse. Thus, reset beginning of cmd + ;; to this line we are on. + (setq prev-cmd-begin lvl1) + ) + + (when (eq (car prev-lvl1) 'block-start) + ;; We have a block start, so remember + (setq prev-block1 prev-lvl1) - (when (eq (nth mlf-stype prev-lvl1) 'decl) - (setq prev-fcn1 prev-lvl1)) - )) + (when (eq (nth mlf-stype prev-lvl1) 'decl) + (setq prev-fcn1 prev-lvl1)) + )) - ;; Do something with comment here ?? - ))) + ;; Do something with comment here ?? + ))) - (list lvl1 prev-lvl1 prev-nonempty prev-code1 prev-block1 prev-fcn1 + (list lvl1 prev-lvl1 prev-lvl2 prev-cmd-begin prev-nonempty prev-code1 prev-block1 prev-fcn1 )))) +;;; Refresh this lvl2 +;; +(defun matlab-refresh-line-context-lvl2 (lvl2 &optional lvl1) + "Refresh the content of this lvl2 context. +Assume ONLY the line this lvl2 context belongs to has changed +and we don't have any caches in later lines." + (matlab-with-context-line lvl2 + (when (not lvl1) (setq lvl1 (matlab-compute-line-context 1))) + ;; cmd begin can be same as self. Check and replace + (when (eq (car lvl2) (nth mlf-previous-command-beginning lvl2)) + (setcdr (nthcdr mlf-previous-command-beginning lvl2) lvl1)) + ;; Replace self. + (setcar lvl2 lvl1) + )) + ;;; Simple Accessors ;; (defun matlab-get-lvl1-from-lvl2 (lvl2) @@ -485,7 +516,20 @@ If LVL2 is nil, compute it." (defun matlab-previous-line (lvl2) "Return the previous line from lvl2 context." (matlab-scan-stat-inc 'prevline) - (nth mlf-previous-line lvl2)) + (nth mlf-previous-line1 lvl2)) + +(defun matlab-previous-line-lvl2 (lvl2) + "Return the previous line from lvl2 context." + (let ((prev (nth mlf-previous-line2 lvl2))) + (if (eq prev t) + (save-excursion + (matlab-scan-stat-inc 'prevline2miss) + (matlab-with-context-line (matlab-previous-line lvl2) + (setq prev (matlab-compute-line-context 2))) + (setcar (nthcdr mlf-previous-line1 lvl2) prev)) + ;; Else, return + (matlab-scan-stat-inc 'prevline2) + prev))) (defun matlab-previous-nonempty-line (lvl2) "Return lvl1 ctxt for previous non-empty line." @@ -514,14 +558,35 @@ If LVL2 is nil, compute it." (save-excursion (matlab-scan-stat-inc 'codemiss) (beginning-of-line) - (forward-comment -100000) ;; Skip over all whitespace and comments. - (setq prev (matlab-compute-line-context 1)) - (setcar (nthcdr mlf-previous-code lvl2) prev)) + (when (not (bobp)) + (forward-comment -100000) ;; Skip over all whitespace and comments. + (setq prev (matlab-compute-line-context 1)) + (setcar (nthcdr mlf-previous-code lvl2) prev))) ;; else record a cache hit (matlab-scan-stat-inc 'code) ) prev)) +(defun matlab-previous-command-begin (lvl2) + "Return lvl1 ctxt for previous non-empty line." + (let ((prev (nth mlf-previous-command-beginning lvl2)) + ) + (if (eq prev t) + ;; Compute it and stash it. + (save-excursion + (matlab-scan-stat-inc 'cmdbeginmiss) + (beginning-of-line) + (when (not (bobp)) + (forward-comment -100000) ;; Skip over all whitespace and comments. + (matlab-scan-beginning-of-command) + ;; TODO!! + (setq prev (matlab-compute-line-context 1)) + (setcar (nthcdr mlf-previous-command-beginning lvl2) prev))) + ;; else record a cache hit + (matlab-scan-stat-inc 'cmdbegin) + ) + prev)) + ;;(defun matlab-previous-fcn-line (lvl2) ;; "Return lvl1 ctxt for previous non-empty line." ;; (let ((prev (nth mlf-previous-fcn lvl2)) diff --git a/matlab.el b/matlab.el index 8162a4f..d0b4438 100644 --- a/matlab.el +++ b/matlab.el @@ -2481,7 +2481,10 @@ parsing state and re-uses that state along the way." Input LVL2 is a pre-scanned context from `matlab-compute-line-context' lvl2. Used internally by `matlab-indent-line', and `matlab-indent-region'." (let* ((i (matlab--calc-indent lvl2))) - (funcall matlab--change-indentation-override i) + (when (funcall matlab--change-indentation-override i) + ;; If the indent changed something, refresh this + ;; context obj. + (matlab-refresh-line-context-lvl2 lvl2)) )) (defun matlab--change-indentation (new-indentation) @@ -2497,13 +2500,13 @@ This function exists so the test harness can override it." nil) ((< diff 0) ;; Too short - Add stuff (indent-to i)) - ((< diff ci) ;; Too much, delete some. + ((<= diff ci) ;; Too much, delete some. (delete-region (- (point) diff) (point))) (t ;; some sort of bug that wants to delete too much. Ignore. nil) )) (if (<= cc ci) (move-to-column (max 0 i))) - )) + (/= 0 diff))) (defun matlab--calc-indent (&optional lvl2) "Return the appropriate indentation for this line as an integer." @@ -2511,19 +2514,26 @@ This function exists so the test harness can override it." (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) ;; The first step is to find the current indentation. ;; This is defined to be zero if all previous lines are empty. - (let* ((ci (if (matlab-line-in-array lvl2) - ;; If we are inside an array continuation, then we shouldn't - ;; need to do anything complicated here b/c we'll just ignore - ;; the returned value in the next step. Return current indentation - ;; of the previous non-empty line. - (matlab-line-indentation (matlab-previous-nonempty-line lvl2)) - - ;; Else, the next line might recommend an indentation based - ;; on it's own context. - (save-excursion (if (not (matlab-prev-line)) - 0 - (matlab-next-line-indentation lvl2))))) + (let* ((ci (cond + ((save-excursion (beginning-of-line) (bobp)) + ;; Beginning of buffer - do no work, just return 0. + 0) + ((matlab-line-in-array lvl2) + ;; If we are inside an array continuation, then we shouldn't + ;; need to do anything complicated here b/c we'll just ignore + ;; the returned value in the next step. Return current indentation + ;; of the previous non-empty line. + (matlab-line-indentation (matlab-previous-nonempty-line lvl2))) + (t + ;; Else, the previous line might recommend an indentation based + ;; on it's own context, like being a block open or continuation. + (let ((prevcmd (or (matlab-previous-code-line lvl2) + (matlab-previous-line lvl2)))) + (matlab-with-context-line prevcmd + (matlab-next-line-indentation lvl2 prevcmd))))) + ) + ;; Compute this line's indentation based on recommendation of previous ;; line. (sem (matlab-calculate-indentation ci lvl2))) @@ -2814,7 +2824,7 @@ LVL2 is a level 2 scan context with info from previous lines." cc))))))))) ))) -(defun matlab-next-line-indentation (lvl2) +(defun matlab-next-line-indentation (lvl2 prevlvl1) "Calculate the indentation for lines following this command line. Assume that the following line does not contribute its own indentation \(as it does in the case of nested functions in the following situations): @@ -2823,13 +2833,16 @@ Assume that the following line does not contribute its own indentation not indenting function bodies. See `matlab-calculate-indentation'." (matlab-navigation-syntax - (let ((lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) - (startpnt (point-at-eol)) + (let ((startpnt (point-at-eol)) + (lvl1 nil) ) (save-excursion - (matlab-with-current-command + (progn ; matlab-with-context-line lvl1 ;;(matlab-beginning-of-command) - (goto-char (point-min)) + ;;(goto-char (point-min)) + ;;(matlab-scan-beginning-of-command lvl1) + (matlab-beginning-of-command) + (back-to-indentation) (setq lvl1 (matlab-compute-line-context 1)) (let ((cc (or (matlab-lattr-block-close startpnt) 0)) @@ -2837,9 +2850,13 @@ See `matlab-calculate-indentation'." (bc (matlab-lattr-block-cont startpnt)) (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) - (hc (and (matlab-last-guess-decl-p) - (matlab-indent-function-body-p) - (matlab-scan-comment-help-p lvl2))) ;(matlab-ltype-help-comm))) + ;; TODO: The old impl of HC here - not sure what it did. point was always on the fcn decl + ;; so this would always be wrong. Leaving out to see what happens. + (hc nil) ;(and (matlab-last-guess-decl-p) + ;(matlab-indent-function-body-p) + ;;;(matlab-ltype-help-comm) + ;(matlab-scan-comment-help-p lvl2) + ;)) (rc (and (/= 0 matlab-comment-anti-indent) (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) ;;(not (matlab-ltype-help-comm)) @@ -4200,7 +4217,8 @@ desired. Optional argument FAST is not used." (let* ((msg "line-info:") (lvl2 (matlab-compute-line-context 2)) (indent (matlab-calculate-indentation (current-indentation) lvl2)) - (nexti (matlab-next-line-indentation lvl2))) + (nexti (matlab-next-line-indentation (matlab-previous-line-lvl2 lvl2) + (matlab-get-lvl1-from-lvl2 lvl2)))) (setq msg (concat msg " Line type: " (symbol-name (car indent)) " This Line: " (int-to-string (nth 1 indent)) From e30091db8e6b11ef7173099317d41020d784f013 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 00:52:49 -0500 Subject: [PATCH 065/168] matlab.el: (matlab-next-line-indentation): Remove navigation syntax, and save excursion, not needed anymore. Stop computing rc, as that didn't seem to be used anymore. --- matlab.el | 170 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 82 deletions(-) diff --git a/matlab.el b/matlab.el index d0b4438..b427ad5 100644 --- a/matlab.el +++ b/matlab.el @@ -2832,75 +2832,80 @@ Assume that the following line does not contribute its own indentation o end---negative indentation except when the 'end' matches a function and not indenting function bodies. See `matlab-calculate-indentation'." - (matlab-navigation-syntax - (let ((startpnt (point-at-eol)) - (lvl1 nil) - ) - (save-excursion - (progn ; matlab-with-context-line lvl1 - ;;(matlab-beginning-of-command) - ;;(goto-char (point-min)) - ;;(matlab-scan-beginning-of-command lvl1) - (matlab-beginning-of-command) + (let ((startpnt (point-at-eol)) + (lvl1 nil) + ) + (save-excursion + (matlab-scan-beginning-of-command lvl1) + ;;(let ((plvl2 (matlab-previous-line-lvl2 lvl2))) + ;; (matlab-previous-command-begin plvl2)) + ;;(matlab-beginning-of-command) - (back-to-indentation) - (setq lvl1 (matlab-compute-line-context 1)) - (let ((cc (or (matlab-lattr-block-close startpnt) 0)) - (end (matlab-line-end-p lvl1)) ;(matlab-lattr-local-end)) - (bc (matlab-lattr-block-cont startpnt)) - (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) - (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) - ;; TODO: The old impl of HC here - not sure what it did. point was always on the fcn decl - ;; so this would always be wrong. Leaving out to see what happens. - (hc nil) ;(and (matlab-last-guess-decl-p) - ;(matlab-indent-function-body-p) - ;;;(matlab-ltype-help-comm) - ;(matlab-scan-comment-help-p lvl2) - ;)) - (rc (and (/= 0 matlab-comment-anti-indent) - (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) - ;;(not (matlab-ltype-help-comm)) - (not (matlab-ltype-continued-comm)) - )) - (ci (current-indentation))) - ;; When the current point is on a line with a function, the value of bc will - ;; reflect the function in a block count iff if matlab-functions-have-end is - ;; true. However, if matlab-indent-function-body-p is false, there should be - ;; no actual indentation, so bc needs to be decremented by 1. Similarly, if - ;; on a line with an end that closes a function, bc needs to be decremented - ;; by 1 if matlab-functions-have-end is true and matlab-indent-function-body-p - ;; is false. However, just to be safe, indentation is not allowed to go - ;; negative. Thus: - (if matlab-functions-have-end - (if (and - (not (matlab-indent-function-body-p)) - (or (matlab-line-declaration-p lvl1) ;(matlab-ltype-function-definition) - (and (matlab-line-end-p lvl1) ;(matlab-lattr-local-end) - (save-excursion - (matlab-backward-sexp t) - (looking-at "function\\b"))))) - (if (> bc 0) - (setq bc (1- bc)) - (if (>= ci matlab-indent-level) - (setq bc -1)))) - ;; Else, funtions don't have ends in this file. - (if (and (matlab-indent-function-body-p) - (matlab-line-declaration-p lvl1)) ; (matlab-ltype-function-definition)) - (setq bc (1+ bc)))) - ;; Remove 1 from the close count if there is an END on the beginning - ;; of this line, since in that case, the unindent has already happened. - (when end (setq cc (1- cc))) - ;; Calculate the suggested indentation. - (+ ci - (* matlab-indent-level bc) - (* matlab-indent-level (or mc 0)) - (* matlab-indent-level (- cc)) - (* (if (listp matlab-case-level) - (cdr matlab-case-level) matlab-case-level) - (or ec 0)) - (if hc matlab-indent-level 0) - (if rc (- 0 matlab-comment-anti-indent) 0) - ))))))) + (back-to-indentation) + (setq lvl1 (matlab-compute-line-context 1)) + (let ((cc (or (matlab-lattr-block-close startpnt) 0)) + (bc (matlab-lattr-block-cont startpnt)) + (end (matlab-line-end-p lvl1)) ;(matlab-lattr-local-end)) + (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) + (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) + ;; TODO: The old impl of HC here - not sure what it did. point was always on the fcn decl + ;; so this would always be wrong. Leaving out to see what happens. + (hc nil) + ;;(and (matlab-last-guess-decl-p) + ;;(matlab-indent-function-body-p) + ;; ;;(matlab-ltype-help-comm) + ;;(matlab-scan-comment-help-p lvl2) + ;;)) + + ;; TODO: Ol impl of RC here - but not sure what this is doing either. It doesn't + ;; seem to ever be t in my tests. + (rc nil) + ;; (and (/= 0 matlab-comment-anti-indent) + ;; (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) + ;; ;;(not (matlab-ltype-help-comm)) + ;; (not (matlab-ltype-continued-comm)) + ;; (message "RC found a thing.") + ;; )) + + (ci (current-indentation))) + ;; When the current point is on a line with a function, the value of bc will + ;; reflect the function in a block count iff if matlab-functions-have-end is + ;; true. However, if matlab-indent-function-body-p is false, there should be + ;; no actual indentation, so bc needs to be decremented by 1. Similarly, if + ;; on a line with an end that closes a function, bc needs to be decremented + ;; by 1 if matlab-functions-have-end is true and matlab-indent-function-body-p + ;; is false. However, just to be safe, indentation is not allowed to go + ;; negative. Thus: + (if matlab-functions-have-end + (if (and + (not (matlab-indent-function-body-p)) + (or (matlab-line-declaration-p lvl1) ;(matlab-ltype-function-definition) + (and (matlab-line-end-p lvl1) ;(matlab-lattr-local-end) + (save-excursion + (matlab-backward-sexp t) + (looking-at "function\\b"))))) + (if (> bc 0) + (setq bc (1- bc)) + (if (>= ci matlab-indent-level) + (setq bc -1)))) + ;; Else, funtions don't have ends in this file. + (if (and (matlab-indent-function-body-p) + (matlab-line-declaration-p lvl1)) ; (matlab-ltype-function-definition)) + (setq bc (1+ bc)))) + ;; Remove 1 from the close count if there is an END on the beginning + ;; of this line, since in that case, the unindent has already happened. + (when end (setq cc (1- cc))) + ;; Calculate the suggested indentation. + (+ ci + (* matlab-indent-level bc) + (* matlab-indent-level (or mc 0)) + (* matlab-indent-level (- cc)) + (* (if (listp matlab-case-level) + (cdr matlab-case-level) matlab-case-level) + (or ec 0)) + (if hc matlab-indent-level 0) + (if rc (- 0 matlab-comment-anti-indent) 0) + ))))) ;;; The return key ============================================================ @@ -4214,20 +4219,21 @@ desired. Optional argument FAST is not used." (defun matlab-show-line-info () "Display type and attributes of current line. Used in debugging." (interactive) - (let* ((msg "line-info:") - (lvl2 (matlab-compute-line-context 2)) - (indent (matlab-calculate-indentation (current-indentation) lvl2)) - (nexti (matlab-next-line-indentation (matlab-previous-line-lvl2 lvl2) - (matlab-get-lvl1-from-lvl2 lvl2)))) - (setq msg (concat msg - " Line type: " (symbol-name (car indent)) - " This Line: " (int-to-string (nth 1 indent)) - " Next Line: " (int-to-string nexti))) - (if (matlab-lattr-cont) - (setq msg (concat msg " w/cont"))) - (if (matlab-lattr-comm) - (setq msg (concat msg " w/comm"))) - (message msg))) + (matlab-navigation-syntax + (let* ((msg "line-info:") + (lvl2 (matlab-compute-line-context 2)) + (indent (matlab-calculate-indentation (current-indentation) lvl2)) + (nexti (matlab-next-line-indentation (matlab-previous-line-lvl2 lvl2) + (matlab-get-lvl1-from-lvl2 lvl2)))) + (setq msg (concat msg + " Line type: " (symbol-name (car indent)) + " This Line: " (int-to-string (nth 1 indent)) + " Next Line: " (int-to-string nexti))) + (if (matlab-lattr-cont) + (setq msg (concat msg " w/cont"))) + (if (matlab-lattr-comm) + (setq msg (concat msg " w/comm"))) + (message msg)))) (provide 'matlab) From 6fe7da0a621bd2d6a57c44ac1e4d9cf957a08d96 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 19:28:48 -0400 Subject: [PATCH 066/168] tests/metest.el (metest-all-syntax-tests): Force files used for intenting to be pre-spaced incorrectly. (metest-condition-case-error-msg): Fix how caught errors are thrown. (met-indents-files): Add mfuncnoendblock.m (metest-indents-randomize-files): New Not really random. (metest-error): Add a buffer excerpt before throwing an error. tests/indents.m, tests/mfuncends: Added more indent tests. tests/mfuncnoendblock.m: Add more content. Enable for use in indentation test. --- tests/indents.m | 41 +++++++++++++++++++++++----------- tests/metest.el | 49 ++++++++++++++++++++++++++++++++++++----- tests/mfuncends.m | 12 +++++----- tests/mfuncnoendblock.m | 38 ++++++++++++++++++++++---------- 4 files changed, 103 insertions(+), 37 deletions(-) diff --git a/tests/indents.m b/tests/indents.m index 6990653..bf20b58 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -13,14 +13,15 @@ function indents(a,b,stuff) stuff {mustBeMember(stuff, { 'this' 'that' 'other' })} % !!8 end % !!4 + locala = a; %#ok localb = b; %#ok localstuff = stuff; %#ok - ends_in_comments_and_strings(); % has end in name + ends_in_comments_and_strings(); % !!4 has end in name % !!4 - + block_starts_in_comments_and_strings(); array_constant_decls(); @@ -28,11 +29,11 @@ function indents(a,b,stuff) continuations_and_block_comments(); -% $$$ !!0 +% $$$ !!0 % $$$ special ignore comments - - has_nested_fcn(); - + + has_nested_fcn(); % !!4 + % !!4 - after ignore comments end % Comment with end in it @@ -52,7 +53,7 @@ function B = ends_in_comments_and_strings() 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"; @@ -64,14 +65,14 @@ function B = ends_in_comments_and_strings() % !!4 E = [ D C]; - + if bar A = E; end; B = A(1:end); % !!4 - + E = B; if baz @@ -80,16 +81,16 @@ function B = ends_in_comments_and_strings() end; B = [ 1 2 ... % is this the end? 3 4 ]; % !!15 - + % !!4 - + if foo A = E; end ... the other end % !! 4 - + code1(), ... code2(); %!!8 @@ -118,7 +119,7 @@ function B = ends_in_comments_and_strings() % !!4 B = A; - + end function out = array_constant_decls() @@ -154,6 +155,12 @@ function out = array_constant_decls() '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 @@ -254,6 +261,14 @@ function B = continuations_and_block_comments % !!4 -blank between this & continuation comment % !!4 - more comments + + if condition1 || ... % !!4 + fcn_call(arg1, ... % !!12 + arg2) % !!21 + line_in_if(); + end % !!4 + + end diff --git a/tests/metest.el b/tests/metest.el index 047c7f7..e0489ed 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -51,6 +51,11 @@ (metest-run 'metest-comment-string-syntax-test) (metest-run 'metest-sexp-counting-test) (metest-run 'metest-sexp-traversal-test) + + ;; Randomize indentation first before indenting + ;; to force the indenter to make changes and give + ;; the cahce and performance a harder problem. + (metest-indents-randomize-files) (metest-run 'metest-indents-test) (metest-run 'metest-parse-test) @@ -84,7 +89,7 @@ (declare (indent 0) (debug t)) `(condition-case err ,@forms - (error (cond ((metest-error (error (car (cdr err))))) + (error (cond (metest-test-error (error (car (cdr err)))) (t (metest-error "Lisp: %s" (error-message-string err)))) 0) )) @@ -238,9 +243,25 @@ (list cnt "test"))) -(defvar met-indents-files '("indents.m" "mclass.m" "blocks.m" "mfuncends.m") +(defvar met-indents-files '("indents.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m") "List of files for running syntactic indentation tests.") +(defun metest-indents-randomize-files () + "Randomize the indentation in the inents test files." + (interactive) + (message "<< Flattening indentation ...") + (let ((matlab-scan-temporal-cache nil)) ;; disable cache for file load + (dolist (F met-indents-files) + (with-current-buffer (metest-find-file F) + (goto-char (point-min)) + (while (not (eobp)) + (beginning-of-line) + (matlab--change-indentation 3) ;;(random 12) + (forward-line 1) + ) + ;; And don't delete - leave it to find for the next test. + )))) + (defvar metest-indents-test (cons "indenting" met-indents-files)) (defvar metest-indent-counts 0) (defun metest-indents-test (F) @@ -327,13 +348,29 @@ Do error checking to provide easier debugging." (error "Test file %s does not exist in %s" file met-testfile-path)) (find-file-noselect F))) +(defvar metest-error-context-lines 4) (defun metest-error (&rest args) "Produce an err with standardized file/line prefix." (declare (indent 1)) - (let ((pre (format "\n%s:%d: Error: " - (file-name-nondirectory (buffer-file-name)) - (line-number-at-pos))) - (post (apply 'format args))) + (let* ((lineno (line-number-at-pos)) + (fname (file-name-nondirectory (buffer-file-name))) + (pre (format "\n%s:%d: Error: " fname lineno)) + (post (apply 'format args)) + (prelines (min lineno metest-error-context-lines))) + (message "\n--vv buffer snip: %s vv--" fname) + (save-excursion + (forward-line (- prelines)) + (while (> prelines 0) + (message "|%s" (buffer-substring-no-properties (point-at-bol) (point-at-eol))) + (forward-line 1) + (setq prelines (1- prelines))) + (message ">%s" (buffer-substring-no-properties (point-at-bol) (point-at-eol))) + (forward-line 1) + (while (and (> metest-error-context-lines prelines) (not (eobp))) + (message "|%s" (buffer-substring-no-properties (point-at-bol) (point-at-eol))) + (forward-line 1) + (setq prelines (1+ prelines)))) + (message "---^^ buffer snip ^^---") (setq metest-test-error t) (error (concat pre post)))) diff --git a/tests/mfuncends.m b/tests/mfuncends.m index deaf490..5a31078 100644 --- a/tests/mfuncends.m +++ b/tests/mfuncends.m @@ -1,10 +1,10 @@ function mfuncends( ) -% Test function that has ends for each function. -% -% Used with test harness to validate indentation and end detection. -% +% Test function that has ends for each function. !!0 +% !!0 +% Used with test harness to validate indentation and end detection. !!0 +% !!0 % %%%function function - + fcn_call(); %!!4 if condition %!!4 @@ -14,7 +14,7 @@ function mfuncends( ) end %!!4 end%!!0 - also no space after end - + function a=fcn_call(inp) %!!0 while inp > 0 %!!4 diff --git a/tests/mfuncnoendblock.m b/tests/mfuncnoendblock.m index e08c568..8e63953 100644 --- a/tests/mfuncnoendblock.m +++ b/tests/mfuncnoendblock.m @@ -1,18 +1,32 @@ %{ Add a block comment at the beginning to skip over. + x % !!2 +% !! 0 %} -function mfuncnoendblock +function mfuncnoendblock % A function file that does not have ends at the end of functions. -% +% !!0 % %%% function nil - -fcn_call(1) - -function fcn_call(idx) - -if idx > 0 - fcn_call(ix-1) -end - - \ No newline at end of file +fcn_call(1) %!!0 + + +function fcn_call(idx) %!!0 + +if idx > 0 %!!0 + fcn_call(ix-1) %!!4 +end %!!0 + +function c=goo(p3) %!!0 +if p3 < 3 %!!0 + if p3 < 0 %!!4 + c = p3 * -3; %!!8 + else %!!4 + c = p3 * 3; %!!8 + for i=1:10 %!!8 + c = c + i; %!!12 + end %!!8 + end %!!4 +else %!!0 + c=p3*4; %!!4 +end %!!0 From 50552dab81f397b1aa48341f4ac9fa4a1556a38b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 19:35:09 -0400 Subject: [PATCH 067/168] matlab-scan.el: (matlab-scan-previous-line-ellipsis-p): Return position, not the column. (matlab-scan-beginning-of-command): Recompute the line cache as we search backward. (matlab-scan-after-change-fcn) ->> rename to (matlab-scan-before-change-fcn) (matlab-scan-setup, matlab-scan-disable): Use rename. matlab.el: (matlab-lattr-block-close): We can sometimes find a function for files with endless functions. Prevent this from acting like an end. (matlab-indent-region): Disable line-to-line caching of lvl2. It isn't stable. Move refreshing of lvl2 here, but comment out. (matlab--indent-line): Stop refreshing lvl2 here. (matlab--change-indentation): Prevent indentation of less than 0. (matlab--calc-indent): Accept a debug sym. (matlab-show-line-info): Use new matlab--calc-indent api. --- matlab-scan.el | 21 ++++++++++----------- matlab.el | 29 ++++++++++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index fbe4fba..dccd6c7 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -643,9 +643,8 @@ Optional PT, if non-nil, means return the point instead of column" (if pt (point) (current-indentation)))))))) (defun matlab-scan-previous-line-ellipsis-p () - "Return the column of the previous line's continuation if there is one. -This is true iff the previous line has an ellipsis, but not if this line -is in an array with an implied continuation." + "Return the position of the previous line's continuation if there is one. +This is true iff the previous line has an ellipsis." (save-excursion (beginning-of-line) (when (not (bobp)) @@ -654,10 +653,8 @@ is in an array with an implied continuation." (csc (nth 8 pps))) ;; If the comment active on eol does NOT start with %, then it must be ;; and ellipsis. - (and csc - (/= (char-after csc) ?\%) - (goto-char csc) - (current-column)))))) + (when (and csc (/= (char-after csc) ?\%)) + csc))))) (defun matlab-scan-beginning-of-command (&optional lvl1) "Return point in buffer at the beginning of this command. @@ -687,7 +684,9 @@ backward over lines that include ellipsis." (if (not prev) (setq found t) ;; Move to prev location if not found. - (goto-char prev)))) + (goto-char prev) + (setq lvl1 (matlab-compute-line-context 1)) + ))) (back-to-indentation) (point)))) @@ -725,20 +724,20 @@ Make sure the cache doesn't exceed max size." nil)) -(defun matlab-scan-after-change-fcn (start end length) +(defun matlab-scan-before-change-fcn (start end &optional length) "Function run in after change hooks." (setq matlab-scan-temporal-cache nil)) (defun matlab-scan-setup () "Setup use of the indent cache for the current buffer." (interactive) - (add-hook 'after-change-functions 'matlab-scan-after-change-fcn t) + (add-hook 'before-change-functions 'matlab-scan-before-change-fcn t) (setq matlab-scan-temporal-cache nil)) (defun matlab-scan-disable () "Setup use of the indent cache for the current buffer." (interactive) - (remove-hook 'after-change-functions 'matlab-scan-after-change-fcn t) + (remove-hook 'before-change-functions 'matlab-scan-before-change-fcn t) (setq matlab-scan-temporal-cache nil)) diff --git a/matlab.el b/matlab.el index b427ad5..21ab984 100644 --- a/matlab.el +++ b/matlab.el @@ -2386,6 +2386,11 @@ Argument START is where to start searching from." ((not (matlab-valid-end-construct-p)) ;; Not a valid end, just move past it. (goto-char nomove)) + ((matlab-line-declaration-p lvl1) + ;; In endless fuction buffers, a function marks the bounds + ;; of other functions, but in this case, it is meaningless, + ;; so do nothing with it. + (goto-char nomove)) (t ;; Lets count these end constructs. (setq v (1+ v)) @@ -2457,9 +2462,12 @@ parsing state and re-uses that state along the way." (unless (and (bolp) (eolp)) ;; This is where we indent each line (setq lvl1 (matlab-compute-line-context 1) - lvl2 (matlab-compute-line-context 2 lvl1 lvl2)) - (matlab--indent-line lvl2) - ) + lvl2 (matlab-compute-line-context 2 lvl1));; lvl2)) + (when (matlab--indent-line lvl2) + ;; If the indent changed something, refresh this + ;; context obj. + ;;(matlab-refresh-line-context-lvl2 lvl2) + )) (forward-line 1) (and pr (progress-reporter-update pr (point)))) (and pr (progress-reporter-done pr)) @@ -2481,16 +2489,12 @@ parsing state and re-uses that state along the way." Input LVL2 is a pre-scanned context from `matlab-compute-line-context' lvl2. Used internally by `matlab-indent-line', and `matlab-indent-region'." (let* ((i (matlab--calc-indent lvl2))) - (when (funcall matlab--change-indentation-override i) - ;; If the indent changed something, refresh this - ;; context obj. - (matlab-refresh-line-context-lvl2 lvl2)) - )) + (funcall matlab--change-indentation-override i))) (defun matlab--change-indentation (new-indentation) "Change the indentation on line to NEW-INDENTATION. This function exists so the test harness can override it." - (let* ((i new-indentation) + (let* ((i (max new-indentation 0)) (ci (current-indentation)) (cc (current-column)) (diff (- ci i))) @@ -2508,7 +2512,7 @@ This function exists so the test harness can override it." (if (<= cc ci) (move-to-column (max 0 i))) (/= 0 diff))) -(defun matlab--calc-indent (&optional lvl2) +(defun matlab--calc-indent (&optional lvl2 debug-sym) "Return the appropriate indentation for this line as an integer." ;; In case it wasn't provided. (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) @@ -2537,6 +2541,8 @@ This function exists so the test harness can override it." ;; Compute this line's indentation based on recommendation of previous ;; line. (sem (matlab-calculate-indentation ci lvl2))) + (when debug-sym + (set debug-sym sem)) ;; simplistic (nth 1 sem))) @@ -4222,7 +4228,8 @@ desired. Optional argument FAST is not used." (matlab-navigation-syntax (let* ((msg "line-info:") (lvl2 (matlab-compute-line-context 2)) - (indent (matlab-calculate-indentation (current-indentation) lvl2)) + (indent nil) + (fullindent (matlab--calc-indent lvl2 'indent)) (nexti (matlab-next-line-indentation (matlab-previous-line-lvl2 lvl2) (matlab-get-lvl1-from-lvl2 lvl2)))) (setq msg (concat msg From 880e1837b126f5796e1cd441bad77ed2351f9fb3 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 19:54:46 -0400 Subject: [PATCH 068/168] matlab.el: (matlab-calculate-indentation-1): when inside parentheticals; capture indentation of start as our previous indentation. Later, ignore all that and just use paren column. Left notes about what else to fix. tests/indents.m: Updated to match new indentation logic. --- matlab.el | 19 +++++++++++++------ tests/indents.m | 6 +++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/matlab.el b/matlab.el index 21ab984..ea6f005 100644 --- a/matlab.el +++ b/matlab.el @@ -2733,9 +2733,11 @@ LVL2 is a level 2 scan context with info from previous lines." (list (if (matlab-line-empty-p lvl1) 'empty (if (matlab-lattr-array-cont) 'array-cont 'code)) ;; Record beginning of the command - (let ((boc (save-excursion - (matlab-beginning-of-command) - (point)))) + (let* ((ci-local ci) ;; we prob want to change this due to continuation. + (boc (save-excursion + (matlab-beginning-of-command) + (setq ci-local (current-indentation)) + (point)))) (condition-case nil (save-excursion (beginning-of-line) @@ -2800,10 +2802,15 @@ LVL2 is a level 2 scan context with info from previous lines." ;; previous line. (let ((cci (current-indentation))) (+ cci matlab-cont-level)) + ;; TODO - this disables indentation MAXs + ;; if we really want to be rid of this + ;; we can dump a bunch of logic above too. ;; apply the maximum limits. - (if (and ind (> (- (current-column) ci) max)) - (+ ci ind) - (current-column)))))))) + ;;(if (and ind (> (- (current-column) ci-local) max)) + ;; (+ ci-local ind) + ;; (current-column)) + (current-column) + )))))) (error ;; Line up to an equals sign. (save-excursion diff --git a/tests/indents.m b/tests/indents.m index bf20b58..ce11bd5 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -150,9 +150,9 @@ function out = array_constant_decls() % 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' %!!24 - 'str' %!!37 - 'str' %!!37 + 'str' %!!37 + 'str' %!!37 + 'str' %!!37 }); %!!35 thing.thing.long.long.longname('str', ... %!!4 From 99d4f7e625cbbe785c469832a617845331d2dbad Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 22:09:41 -0400 Subject: [PATCH 069/168] matlab-syntax.el: (matlab-cursor-in-string-or-comment) (matlab-cursor-in-comment matlab-cursor-in-string) (matlab-beginning-of-string-or-comment): Convert to a single line impl. Convert to defsubst. (matlab-end-of-string-or-comment) (matlab-beginning-of-outer-list) (matlab-end-of-outer-list): New --- matlab-syntax.el | 57 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 8ff591d..7bc840e 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -270,9 +270,7 @@ and `matlab--scan-line-for-unterminated-string' for specific details." 'font-lock-comment-face) ) -;;; -;;; EXPERIMENTAL INTEGRATION: -;;; +;;; SETUP ;; ;; Connect our special logic into a running MATLAB Mode ;; replacing existing mechanics. @@ -313,23 +311,20 @@ Safe to use in `matlab-mode-hook'." ;;; Syntax Testing for Strings and Comments ;; ;; These functions detect syntactic context based on the syntax table. -(defun matlab-cursor-in-string-or-comment () +(defsubst matlab-cursor-in-string-or-comment () "Return non-nil if the cursor is in a valid MATLAB comment or string." - (let* ((pps (syntax-ppss (point)))) - (nth 8 pps))) + (nth 8 (syntax-ppss (point)))) -(defun matlab-cursor-in-comment () +(defsubst matlab-cursor-in-comment () "Return t if the cursor is in a valid MATLAB comment." - (let* ((pps (syntax-ppss (point)))) - (nth 4 pps))) + (nth 4 (syntax-ppss (point)))) -(defun matlab-cursor-in-string (&optional incomplete) +(defsubst matlab-cursor-in-string (&optional incomplete) "Return t if the cursor is in a valid MATLAB character vector or string scalar. Note: INCOMPLETE is now obsolete If the optional argument INCOMPLETE is non-nil, then return t if we are in what could be a an incomplete string. (Note: this is also the default)" - (let* ((pps (syntax-ppss (point)))) - (nth 3 pps))) + (nth 3 (syntax-ppss (point)))) (defun matlab-cursor-comment-string-context (&optional bounds-sym) "Return the comment/string context of cursor for the current line. @@ -378,13 +373,25 @@ bounds of the string or comment the cursor is in" ;; Return the syntax syntax)) -(defun matlab-beginning-of-string-or-comment () +(defsubst matlab-beginning-of-string-or-comment () "If the cursor is in a string or comment, move to the beginning. Returns non-nil if the cursor moved." (let* ((pps (syntax-ppss (point)))) - (when (nth 8 pps) (goto-char (nth 8 pps)) - ))) + (when (nth 8 pps) (goto-char (nth 8 pps)) ))) +(defun matlab-end-of-string-or-comment () + "If the cursor is in a string or comment, move to the end. +Returns non-nil if the cursor moved." + (let* ((pps (syntax-ppss (point)))) + (when (nth 8 pps) + ;; syntax-ppss doesn't have the end, so go to the front + ;; and then skip forward. + (goto-char (nth 8 pps)) + (if (nth 3 pps) + (goto-char (scan-sexps (point) 1)) + (forward-comment 1)) + ))) + ;;; Block Comment handling ;; ;; Old version block comments were handled in a special way. @@ -404,7 +411,7 @@ Block comment indicators must be on a line by themselves.") (looking-at matlab-block-comment-start-re))) (defun matlab-ltype-block-comment-end () - "Return non-nil if the current line is a block comment start." + "Return non-nil if the current line is a block comment end." (save-excursion (beginning-of-line) (looking-at matlab-block-comment-end-re))) @@ -459,6 +466,24 @@ comment and string crossing." (up-list count nil t)) ;; will this correctly ignore comments, etc? )) + +(defsubst matlab-beginning-of-outer-list () + "If the cursor is in a list, move to the beginning of outermost list. +Returns non-nil if the cursor moved." + (let* ((pps (syntax-ppss (point)))) + (when (nth 9 pps) (goto-char (car (nth 9 pps))) ))) + +(defun matlab-end-of-outer-list () + "If the cursor is in a, move to the end of the outermost list.. +Returns non-nil if the cursor moved." + (let* ((pps (syntax-ppss (point)))) + (when (nth 9 pps) + ;; syntax-ppss doesn't have the end, so go to the front + ;; and then skip forward. + (goto-char (car (nth 9 pps))) + (goto-char (scan-sexps (point) 1)) + ))) + ;;; Syntax Compat functions ;; ;; Left over old APIs. Delete these someday. From 123e38e4a9915279de6524a9cf4b1d55bc393215 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 14 Mar 2021 22:11:13 -0400 Subject: [PATCH 070/168] matlab-scan.el: (matlab-kwt-*): New keyword regex caches. (matlab-keyword-regex, matlab--keyword-regex): New Create regex for keywords from the keyword table. (matlab-re-search-keyword-forward, matlab-re-search-keyword-backward): New search routines that scan for regex outside of strings, comments, and parens. --- matlab-scan.el | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/matlab-scan.el b/matlab-scan.el index dccd6c7..a1ee78c 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -60,6 +60,63 @@ matlab-block-keyword-list) ans) "Keyword table for fast lookups of different keywords and their purpose.") +(defvar matlab-kwt-all nil) +(defvar matlab-kwt-decl nil) +(defvar matlab-kwt-end nil) + +(defun matlab-keyword-regex (types) + "Find keywords that match TYPES and return optimized regexp. +Caches some found regexp to retrieve them faster." + (cond + ((or (eq types nil) (eq types 'all)) + (or matlab-kwt-all (setq matlab-kwt-all (matlab--keyword-regex nil)))) + ((equal types '(decl)) + (or matlab-kwt-decl (setq matlab-kwt-decl (matlab--keyword-regex types)))) + ((equal types '(end)) + (or matlab-kwt-end (setq matlab-kwt-end (matlab--keyword-regex types)))) + (t + (setq matlab-kwt-all (matlab--keyword-regex types))))) + +(defun matlab--keyword-regex (types) + "Find keywords that match TYPES and return an optimized regexp." + (let ((lst nil)) + (mapc (lambda (C) (when (or (null types) (memq (cdr C) types)) (push (car C) lst))) + matlab-block-keyword-list) + (regexp-opt lst 'symbols))) + +;;; Searching for keywords +;; +;; These utilities will simplify searching for code bits by skipping +;; anything in a comment or string. +(defun matlab-re-search-keyword-forward (regexp &optional bound noerror) + "Like `re-search-forward' but will not match content in strings or comments." + (let ((ans nil)) + (save-excursion + (while (and (not ans) + (setq ans (re-search-forward regexp bound noerror))) + (cond ((matlab-end-of-string-or-comment) + ;; We landed in a string this time through, so clear + ;; the answer and skip the rest of it. + (setq ans nil)) + ((matlab-end-of-outer-list) + (setq ans nil)) + ))) + (when ans (goto-char ans)))) + +(defun matlab-re-search-keyword-backward (regexp &optional bound noerror) + "Like `re-search-backward' but will not match content in strings or comments." + (let ((ans nil)) + (save-excursion + (while (and (not ans) + (setq ans (re-search-backward regexp bound noerror))) + (cond ((matlab-beginning-of-string-or-comment) + ;; We landed in a string this time through, so clear + ;; the answer and skip the rest of it. + (setq ans nil)) + ((matlab-beginning-of-outer-list) + (setq ans nil)) + ))) + (when ans (goto-char ans)))) ;;; Context Parsing ;; From cf18bc07fc3602c647664426b5e2fa9f4109f753 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 15 Mar 2021 13:44:11 -0400 Subject: [PATCH 071/168] matlab.el: (matlab-guess-script-type): After finding code line goto bol so declaration regex works. --- matlab.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index ea6f005..c8a5597 100644 --- a/matlab.el +++ b/matlab.el @@ -196,7 +196,9 @@ Returns one of 'empty, 'script, 'function, 'class." (goto-char (point-min)) (if (matlab-find-code-line) ;; We found some code, what is it? - (if (looking-at matlab-defun-regex) + (if (save-excursion + (beginning-of-line) + (looking-at matlab-defun-regex)) ;; A match - figure out the type of thing. (let ((str (match-string-no-properties 1))) (cond ((string= str "function") @@ -2617,6 +2619,7 @@ LVL2 is a level 2 scan context with info from previous lines." (if (and (not (matlab-indent-function-body-p)) (save-excursion (beginning-of-line) + ;; TODO - maybe replace this? Not usually used. (matlab-beginning-of-enclosing-defun))) (setq ci (+ ci matlab-indent-level)) ;; If no intrinsic indentation, do not change from ci. From 39f1178516bd6a8814976c5ab81da49fbc5395a2 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 15 Mar 2021 13:52:16 -0400 Subject: [PATCH 072/168] metest.el: (metest-indents-randomize-files): Force matlab-mode to re-run so script type is recomputed after extra indent added. mfunspacey.m: New file for script type detection tests. --- tests/metest.el | 4 +++- tests/mfuncspacey.m | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/mfuncspacey.m diff --git a/tests/metest.el b/tests/metest.el index e0489ed..b7405fe 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -94,7 +94,7 @@ 0) )) -(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" ) +(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" "mfuncspacey.m" ) "List of files for running end detection tests on.") (defvar metest-end-detect-test (cons "END detection" met-end-detect-files)) @@ -260,6 +260,8 @@ (forward-line 1) ) ;; And don't delete - leave it to find for the next test. + ;; but we do want to restart the mode and force a re-guess of the file type. + (matlab-mode) )))) (defvar metest-indents-test (cons "indenting" met-indents-files)) diff --git a/tests/mfuncspacey.m b/tests/mfuncspacey.m new file mode 100644 index 0000000..607af51 --- /dev/null +++ b/tests/mfuncspacey.m @@ -0,0 +1,11 @@ + function mfuncspacey( ) + % Test function that has ends for each function. + % + % Used with test harness to validate indentation and end detection. + % + % %%%function function + + fcn_call(); + + end + From 9d41e46c98a213eb21708cae1f5da53264c7389b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 15 Mar 2021 21:52:54 -0400 Subject: [PATCH 073/168] matlab-scan.el: Make accessing keywords easier. (matlab-keyword-p): New (matlab-compute-line-context-lvl-1): Use above. matlab-syntax.el: Support command dual as a string in the syntax tabe. (matlab--command-dual-syntax): New syntax type (matlab--syntax-propertize): Call scanner for command-dual. (matlab--scan-line-for-command-dual): New. (matlab--font-lock-syntactic-face): Detect command dual and provide that face. Detect cellbreak comments and provide that face. (matlab-cursor-comment-string-context): Also detect commanddual strings, and return that status. matlab.el: Revamp font lock keywords to take advantage of updates in `matlab--font-lock-syntactic-face' updates, and to just lean into use of defface. (matlab-show-mlint-warnings, matlab-highlight-cross-function-variables): Make safe local variables. (matlab-commanddual-string-face): New variable. (matlab-font-lock-adjustments): Delete as a function and hook setup. Replace with just the classic defface calls, but cleaned up to also :inherit from base faces. (matlab-file-basic-font-lock-keywords): Remove cell break code. tests/metest.el: Add tests for commanddual strings. (metest-comment-string-syntax-test): Add ability to test for commanddual strings. tests/stringtest.m: Add some command dual examples. --- matlab-scan.el | 22 +++++--- matlab-syntax.el | 53 +++++++++++++++++--- matlab.el | 122 +++++++++++++-------------------------------- tests/metest.el | 4 +- tests/stringtest.m | 11 ++++ 5 files changed, 109 insertions(+), 103 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index a1ee78c..6e56ffe 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -60,6 +60,17 @@ matlab-block-keyword-list) ans) "Keyword table for fast lookups of different keywords and their purpose.") + +(defun matlab-keyword-p (word) + "Non nil if WORD is a keyword. +If word is a number, it is a match-string index for the current buffer." + (let* ((local-word (if (numberp word) + (match-string-no-properties word) + word)) + (sym (intern-soft local-word matlab-keyword-table))) + (and sym (symbol-value sym)))) + + (defvar matlab-kwt-all nil) (defvar matlab-kwt-decl nil) (defvar matlab-kwt-end nil) @@ -97,6 +108,7 @@ Caches some found regexp to retrieve them faster." (cond ((matlab-end-of-string-or-comment) ;; We landed in a string this time through, so clear ;; the answer and skip the rest of it. + ;; TODO: can it skip over all adjacent comments too? (setq ans nil)) ((matlab-end-of-outer-list) (setq ans nil)) @@ -240,17 +252,15 @@ in a single call using fastest methods." ;; If not in parens, this might be a keyword. ;; Look up our various keywords. - (let* ((word (match-string-no-properties 0)) - (sym (intern-soft word matlab-keyword-table)) - ) - (if sym - (if (eq (symbol-value sym) 'end) + (let* ((symval (matlab-keyword-p 0))) + (if symval + (if (eq symval 'end) ;; Special end keyword is in a class all it's own (setq ltype 'end) ;; If we found this in our keyword table, then it is a start ;; of a block with a subtype. (setq ltype 'block-start - stype (symbol-value sym))) + stype symval)) ;; Else - not a sym - just some random code. (setq ltype 'code) )))) diff --git a/matlab-syntax.el b/matlab-syntax.el index 7bc840e..5a2c7bf 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -131,6 +131,10 @@ (put ',symbol 'syntax-table ,symbol) )) +(matlab--syntax-symbol matlab--command-dual-syntax '(15 . nil) ;; Generic string + "Syntax placed on end-of-line for unterminated strings.") +(put 'matlab--command-dual-syntax 'command-dual t) ;; Font-lock cookie + (matlab--syntax-symbol matlab--unterminated-string-syntax '(15 . nil) ;; Generic string end "Syntax placed on end-of-line for unterminated strings.") (put 'matlab--unterminated-string-syntax 'unterminated t) ;; Font-lock cookie @@ -159,6 +163,15 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ;; Apply properties (while (and (not (>= (point) (or end (point-max)))) (not (eobp))) + ;; Commandl line dual comes first to prevent wasting time + ;; in later checks. + (beginning-of-line) + (when (matlab--scan-line-for-command-dual) + (matlab--put-char-category (point) 'matlab--command-dual-syntax) + (end-of-line) + (matlab--put-char-category (point) 'matlab--command-dual-syntax) + ) + ;; Multiple ellipsis can be on a line. Find them all (beginning-of-line) (while (matlab--scan-line-for-ellipsis) @@ -188,6 +201,17 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (forward-line 1)) ))) +(declare-function matlab-keyword-p "matlab-scan") + +(defun matlab--scan-line-for-command-dual (&optional debug) + "Scan this line for command line duality strings." + ;; Note - add \s$ b/c we'll add that syntax to the first letter, and it + ;; might still be there during an edit! + (when (looking-at "^\\s-*\\([a-zA-Z_]\\(?:\\w\\|\\s_\\)*\\)\\s-+\\(\\s$\\|\\w\\|\\s_\\)") + ;; This is likely command line dual for a function. + (when (not (matlab-keyword-p 1)) + (goto-char (match-beginning 2))))) + (matlab--syntax-symbol matlab--transpose-syntax '(3 . nil) ;; 3 = symbol "Treat ' as non-string when used as transpose.") @@ -264,11 +288,21 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (if (nth 3 pps) ;; This is a string. Check the start char to see if it was ;; marked as an unterminate string. - (if (get-text-property (nth 8 pps) 'unterminated) - 'matlab-unterminated-string-face - 'font-lock-string-face) - 'font-lock-comment-face) - ) + (cond ((get-text-property (nth 8 pps) 'unterminated) + 'matlab-unterminated-string-face) + ((get-text-property (nth 8 pps) 'command-dual) + 'matlab-commanddual-string-face) + (t + 'font-lock-string-face)) + + ;; Not a string, must be a comment. Check to see if it is a + ;; cellbreak comment. + (cond ((and (< (nth 8 pps) (point-max)) + (= (char-after (1+ (nth 8 pps))) ?\%)) + 'matlab-cellbreak-face) + (t + 'font-lock-comment-face)) + )) ;;; SETUP ;; @@ -347,9 +381,12 @@ bounds of the string or comment the cursor is in" (goto-char start) ;; Prep for extra checks. (setq syntax (cond ((eq (nth 3 pps) t) - (if (= (following-char) ?') - 'charvector - 'string)) + (cond ((= (following-char) ?') + 'charvector) + ((= (following-char) ?\") + 'string) + (t + 'commanddual))) ((eq (nth 3 pps) ?') 'charvector) ((eq (nth 3 pps) ?\") diff --git a/matlab.el b/matlab.el index c8a5597..2fbee27 100644 --- a/matlab.el +++ b/matlab.el @@ -418,15 +418,15 @@ This will only work if `matlab-highlight-block-match-flag' is non-nil." "*If non-nil, show mlint warnings." :group 'matlab :type 'boolean) - (make-variable-buffer-local 'matlab-show-mlint-warnings) +(put 'matlab-show-mlint-warnings 'safe-local-variable #'booleanp) (defcustom matlab-highlight-cross-function-variables nil "*If non-nil, highlight cross-function variables." :group 'matlab :type 'boolean) - (make-variable-buffer-local 'matlab-highlight-cross-function-variables) +(put 'matlab-highlight-cross-function-variables 'safe-local-variable #'booleanp) (defcustom matlab-return-add-semicolon nil "*If non nil, check to see a semicolon is needed when RET is pressed." @@ -464,6 +464,9 @@ This will only work if `matlab-highlight-block-match-flag' is non-nil." (defvar matlab-unterminated-string-face 'matlab-unterminated-string-face "Self reference for unterminated string face.") +(defvar matlab-commanddual-string-face 'matlab-commanddual-string-face + "Self reference for command dual string face.") + (defvar matlab-simulink-keyword-face 'matlab-simulink-keyword-face "Self reference for simulink keywords.") @@ -476,95 +479,40 @@ This will only work if `matlab-highlight-block-match-flag' is non-nil." (defvar matlab-cellbreak-face 'matlab-cellbreak-face "Self reference for cellbreaks.") -(defun matlab-font-lock-adjustments () - "Make adjustments for font lock. -If font lock is not loaded, lay in wait." - (if (and (featurep 'custom) (fboundp 'custom-declare-variable)) +(defface matlab-unterminated-string-face + '((t :inherit font-lock-string-face + :underline t)) + "*Face used to highlight unterminated strings." + :group 'matlab) - (progn - (defface matlab-unterminated-string-face - (list - (list t - (list :background (face-background font-lock-string-face) - :foreground (face-foreground font-lock-string-face) - :underline t))) - "*Face used to highlight unterminated strings." - :group 'matlab) - (defface matlab-simulink-keyword-face - (list - (list t - (list :background (face-background font-lock-type-face) - :foreground (face-foreground font-lock-type-face) - :underline t))) - "*Face used to highlight simulink specific functions." - :group 'matlab) - (defface matlab-nested-function-keyword-face - (list - (list t - (list :slant 'italic))) - "*Face to use for cross-function variables.") - (defface matlab-cross-function-variable-face - (list - (list t - (list :weight 'bold - :slant 'italic))) - "*Face to use for cross-function variables." - :group 'matlab) - (defface matlab-cellbreak-face - (list - (list t - (list :background (face-background font-lock-comment-face) - :foreground (face-foreground font-lock-comment-face) - :overline t - :bold t))) - "*Face to use for cellbreak %% lines.") - ) +(defface matlab-commanddual-string-face + '((t :inherit font-lock-string-face + :slant italic)) + "*Face used to highlight command dual string equivalent." + :group 'matlab) - ;; Now, lets make the unterminated string face - (cond ((facep 'font-lock-string-face) - (copy-face 'font-lock-string-face - 'matlab-unterminated-string-face)) - (t - (make-face 'matlab-unterminated-string-face))) - (matlab-set-face-underline 'matlab-unterminated-string-face t) +(defface matlab-simulink-keyword-face + '((t :inherit font-lock-type-face + :underline t)) + "*Face used to highlight simulink specific functions." + :group 'matlab) - ;; Now make some simulink faces - (cond ((facep 'font-lock-type-face) - (copy-face 'font-lock-type-face 'matlab-simulink-keyword-face)) - (t - (make-face 'matlab-simulink-keyword-face))) - (matlab-set-face-underline 'matlab-simulink-keyword-face t) +(defface matlab-nested-function-keyword-face + '((t :inherit font-lock-keyword-face + :slant italic)) + "*Face to use for cross-function variables.") - ;; Now make some nested function/end keyword faces - (cond ((facep 'font-lock-type-face) - (copy-face 'font-lock-type-face 'matlab-nested-function-keyword-face)) - (t - (make-face 'matlab-nested-function-keyword-face))) +(defface matlab-cross-function-variable-face + '((t :weight bold + :slant italic)) + "*Face to use for cross-function variables." + :group 'matlab) - ;; Now make some cross-function variable faces - (cond ((facep 'font-lock-type-face) - (copy-face 'font-lock-type-face 'matlab-cross-function-variable-face)) - (t - (make-face 'matlab-cross-function-variable-face))) - (matlab-set-face-bold 'matlab-cross-function-variable-face t) - - ;; Now make some cellbreak variable faces - (cond ((facep 'font-comment-face) - (copy-face 'font-lock-comment-face 'matlab-cellbreak-face)) - (t - (make-face 'matlab-cellbreak-face))) - (matlab-set-face-bold 'matlab-cellbreak-face t) - (condition-case nil - (set-face-attribute 'matlab-cellbreak-face nil :overline t) - (error nil)) - ) - (remove-hook 'font-lock-mode-hook 'matlab-font-lock-adjustments)) - -;; Make the adjustments for font lock after it's loaded. -;; I found that eval-after-load was unreliable. -(if (featurep 'font-lock) - (matlab-font-lock-adjustments) - (add-hook 'font-lock-mode-hook 'matlab-font-lock-adjustments)) +(defface matlab-cellbreak-face + '((t :inherit font-lock-comment-face + :overline t + :bold t)) + "*Face to use for cellbreak %% lines.") ;;; MATLAB mode variables ===================================================== @@ -1052,8 +1000,6 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (3 'underline prepend) ;else part (if applicable) (4 font-lock-comment-face prepend) ;commented out part. ) - ;; Cell mode breaks get special treatment - '("^\\s-*\\(%%[^\n]*\n\\)" (1 matlab-cellbreak-face append)) ;; Highlight cross function variables '(matlab-font-lock-cross-function-variables-match (1 matlab-cross-function-variable-face prepend)) diff --git a/tests/metest.el b/tests/metest.el index b7405fe..4525f14 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -137,7 +137,7 @@ (with-current-buffer buf (goto-char (point-min)) ;;(message ">> Starting string/comment detect loop in %S" (current-buffer)) - (while (re-search-forward "#\\([csveb]\\)#" nil t) + (while (re-search-forward "#\\([csvebd]\\)#" nil t) (goto-char (match-end 1)) (let ((md (match-data)) (mc (match-string 1)) @@ -149,6 +149,7 @@ (and (string= "s" mc) (eq 'string qd)) (and (string= "c" mc) (eq 'comment qd)) (and (string= "e" mc) (eq 'ellipsis qd)) + (and (string= "d" mc) (eq 'commanddual qd)) ) (metest-error "Syntax Test Failure @ char %d: Expected %s but found %S" (point) @@ -157,6 +158,7 @@ ((string= mc "s") "string") ((string= mc "c") "comment") ((string= mc "e") "ellipsis") + ((string= mc "d") "commanddual") (t "unknown test token")) qd)) ;; Test 2 - is match-data unchanged? diff --git a/tests/stringtest.m b/tests/stringtest.m index aec5e80..dd4e377 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -1,3 +1,4 @@ +% -*- matlab-show-mlint-warnings: nil -*- %% Tests for char vector and string handling. % % #c# @@ -164,3 +165,13 @@ not_commented(); %{ just a regular comment #c# %} should_be_comment #c# % Normal comment #c# + + +%% Command line dual #c# +% Note: stuff after a symbol treated as string + +funcall _this is string input to function #d#_ +fun_call _this is also string input to a function #d#_ + + +%% END From dbb6fc6757bc112f11362462de12cf1cf225b35f Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 15 Mar 2021 23:31:17 -0400 Subject: [PATCH 074/168] matlab-scan.el: (matlab-block-keyword-list): Add more keywords. (matlab-compute-line-context-lvl-1): improve keyword detection to handle the new types of keywords that don't start blocks. Improve end of line detection to distinguish command dual from ellipsis. (matlab-line-commanddual-p): New (matlab-scan-previous-line-ellipsis-p): Improve how ellipsis is found to be more explicit. (matlab-describe-line-indent-context): Show command dual in output. matlab.el: (matlab-lattr-cont): Improve how ellipsis is found to be more explicit. indents.el: Add more keyword styles around to deal with command dual problems. --- matlab-scan.el | 51 ++++++++++++++++++++++++++++++++++--------------- matlab.el | 5 +++-- tests/indents.m | 39 ++++++++++++++++++++++++++----------- 3 files changed, 67 insertions(+), 28 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 6e56ffe..525478f 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -46,11 +46,17 @@ ("for" . ctrl) ("parfor" . ctrl) ("while" . ctrl) + ("spmd" . ctrl) ("switch" . ctrl) ("case" . case) ("otherwise" . case) ("try" . ctrl) ("catch" . mid) + ("break" . keyword) + ("continue" . keyword) + ("return" . keyword) + ("global" . vardecl) + ("persistent" . vardecl) ) "List of keywords that are part of code blocks.") @@ -254,16 +260,25 @@ in a single call using fastest methods." ;; Look up our various keywords. (let* ((symval (matlab-keyword-p 0))) (if symval - (if (eq symval 'end) - ;; Special end keyword is in a class all it's own - (setq ltype 'end) - ;; If we found this in our keyword table, then it is a start - ;; of a block with a subtype. + (cond + ;; Special end keyword is in a class all it's own + ((eq symval 'end) + (setq ltype 'end)) + ;; If we found this in our keyword table, then it is a start + ;; of a block with a subtype. + ((memq symval '(decl args mcos ctrl mid case)) (setq ltype 'block-start stype symval)) - ;; Else - not a sym - just some random code. - (setq ltype 'code) - )))) + ;; Some keywords aren't related to blocks with indentation + ;; controls. Those are treated as code, with a type. + ((memq symval '(keyword vardecl)) + (setq ltype 'code + stype symval)) + ;; Else - not a sym - just some random code. + (t + (setq ltype 'code))) + (setq ltype 'code)) + ))) ;; Looking at a close paren. ((and (< 0 paren-depth) (looking-at "\\s)")) @@ -288,8 +303,10 @@ in a single call using fastest methods." ;; If we have something, record what it is. (when csc (setq ec-col csc - ec-type (if (= (char-after csc) ?\%) 'comment 'ellipsis))) ;; type - ) + ec-type (cond ((= (char-after csc) ?\%) 'comment) + ((= (char-after csc) ?\.) 'ellipsis) + (t 'commanddual))) + )) (list ltype stype pt indent start paren-depth paren-inner-char paren-inner-col paren-outer-char paren-outer-point paren-delta @@ -354,10 +371,13 @@ All lines that start with a comment end with a comment." (current-column)))) (defsubst matlab-line-ellipsis-p (lvl1) - "Return if this line ends with a comment. -All lines that start with a comment end with a comment." + "Return if this line ends with a comment." (eq (nth mlf-end-comment-type lvl1) 'ellipsis)) +(defsubst matlab-line-commanddual-p (lvl1) + "Return if this line ends with command duality string." + (eq (nth mlf-end-comment-type lvl1) 'commanddual)) + (defsubst matlab-line-block-comment-start (lvl1) "Return the start of the block comment we are in, or nil." (when (and (matlab-line-comment-p lvl1) @@ -718,9 +738,9 @@ This is true iff the previous line has an ellipsis." (forward-char -1) (let* ((pps (syntax-ppss (point))) (csc (nth 8 pps))) - ;; If the comment active on eol does NOT start with %, then it must be - ;; and ellipsis. - (when (and csc (/= (char-after csc) ?\%)) + ;; Ellipsis start has a syntax of 11 (comment-start). + ;; Other comments have high-bit flags, so don't == 11. + (when (and csc (= (car (syntax-after csc)) 11)) csc))))) (defun matlab-scan-beginning-of-command (&optional lvl1) @@ -920,6 +940,7 @@ Make sure the cache doesn't exceed max size." extraclose (cond ((eq (nth mlf-end-comment-type lvl1) 'comment) "%") ((eq (nth mlf-end-comment-type lvl1) 'ellipsis) "...") + ((eq (nth mlf-end-comment-type lvl1) 'commanddual) "-command dual") (t "")) (if (matlab-line-end-comment-column lvl1) (format " %d" (matlab-line-end-comment-column lvl1)) diff --git a/matlab.el b/matlab.el index 2fbee27..0944758 100644 --- a/matlab.el +++ b/matlab.el @@ -2202,8 +2202,9 @@ based on what it ends with." (csc (nth 8 pps))) (or ;; When the line ends with a comment, it might be an ellipsis. - ;; Can only be tail comment w/ no % start if it is an ellipsis. - (and csc (/= (char-after csc) ?\%)) + ;; Ellipsis start has a syntax of 11 (comment-start). + ;; Other comments have high-bit flags, so don't == 11. + (and csc (= (car (syntax-after csc)) 11)) ;; If the line doesn't end in ..., but we have optional ..., then ;; use this annoying heuristic. diff --git a/tests/indents.m b/tests/indents.m index ce11bd5..a25d910 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -1,4 +1,4 @@ -function indents(a,b,stuff) +function indents(a,b,stuff,cmddual1fake,cmddual2fake) % Help text % !!0 % of many lines @@ -11,18 +11,27 @@ function indents(a,b,stuff) 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 - ends_in_comments_and_strings(); % !!4 has end in name + 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(); + block_starts_in_comments_and_strings(cmddual1fake,cmddual2fake); array_constant_decls(); % !!4 @@ -98,7 +107,7 @@ function B = ends_in_comments_and_strings() code1() ... B = [ B A ]; % !!4 - + str = 'This is a char array with ... in it'; foo(str); % !!4 @@ -175,25 +184,31 @@ function out = array_constant_decls() end -function C = block_starts_in_comments_and_strings() +function C = block_starts_in_comments_and_strings(varargin) % !!0 C = 0; - if true % if true + if varargin{1} % if true % !!8 else % !!4 - - % !!8 + + % !!8 end % if true % see previous function % !!4 for x=1:length(C) % !!4 + if varargin{2} % !!8 + continue % !!12 + end % !!8 - % !!8 + break % !!8 + % !!14 + + %!!8 end switch foo() %!!4 @@ -276,10 +291,12 @@ function has_nested_fcn plot(1:10); %!!4 - function am_nested_fcn %!!4 + A = 1; + + function am_nested_fcn() %!!4 % help % !!4 - code(); + code(A); %!!8 end From 3730fb360ec41abddde9a09de71adf0769b91dcf Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 16 Mar 2021 19:09:58 -0400 Subject: [PATCH 075/168] matlab-syntax.el: (matlab-syntax-support-command-dual): New way to control handling command dual syntax in the syntax table. (matlab--syntax-propertize): Obey above config by skipping command dual scanning if nil. tests/stringtest.m: Add local variables to enable matlab-syntax-support-command-dual for this file --- matlab-syntax.el | 23 +++++++++++++++-------- tests/stringtest.m | 8 +++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 5a2c7bf..d852fe8 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -30,6 +30,12 @@ (require 'matlab-compat) ;;; Code: +(defvar matlab-syntax-support-command-dual nil + "Non-nil means to support command dual for indenting and syntax highlight. +Does not work well in classes with properties with datatypes.") +(make-variable-buffer-local 'matlab-syntax-support-command-dual) +(put 'matlab-syntax-support-command-dual 'safe-local-variable #'booleanp) + (defvar matlab-syntax-table (let ((st (make-syntax-table (standard-syntax-table)))) @@ -163,14 +169,15 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ;; Apply properties (while (and (not (>= (point) (or end (point-max)))) (not (eobp))) - ;; Commandl line dual comes first to prevent wasting time - ;; in later checks. - (beginning-of-line) - (when (matlab--scan-line-for-command-dual) - (matlab--put-char-category (point) 'matlab--command-dual-syntax) - (end-of-line) - (matlab--put-char-category (point) 'matlab--command-dual-syntax) - ) + (when matlab-syntax-support-command-dual + ;; Commandl line dual comes first to prevent wasting time + ;; in later checks. + (beginning-of-line) + (when (matlab--scan-line-for-command-dual) + (matlab--put-char-category (point) 'matlab--command-dual-syntax) + (end-of-line) + (matlab--put-char-category (point) 'matlab--command-dual-syntax) + )) ;; Multiple ellipsis can be on a line. Find them all (beginning-of-line) diff --git a/tests/stringtest.m b/tests/stringtest.m index dd4e377..f266baa 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -172,6 +172,12 @@ not_commented(); funcall _this is string input to function #d#_ fun_call _this is also string input to a function #d#_ - + +%{ +% Local Variables: +% matlab-syntax-support-command-dual: t +% End: +%} + %% END From e0a7d52385fb2fd9fc391140cfc6e5695c2f7ae7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 18 Mar 2021 21:48:16 -0400 Subject: [PATCH 076/168] matlab-scan.el: (matlab-compute-line-context): Don't change the match data. (matlab-scan-end-of-command): New matlab-syntax.el: Doc string fixes. matalb.el: (matlab-find-prev-code-line): Now uses matlab-scan-beginning-of-command instead of old version. (matlab-with-current-command): Now uses matlab-scan-beginning-of-command AND matlab-scan-end-of-command to build the narrowed region. (matlab-cursor-on-valid-block-start): Make this immune to changes in match-data from various conditions in the cond statement. Use new matlab-scan-beginning-of-command instead of old version. (matlab-beginning-of-command): Now use matlab-scan-beginning-of-command as default impl. Delete this fcn later. (matlab-lattr-block-close): Track original starting pos better, grab line context after moving to start. tests/metest.el: Include new continuations.m test file. tests/continuations.m: New test file tests/indents.m: Move a couple tests into continuations.m --- matlab-scan.el | 70 ++++++++++++++++++----- matlab-syntax.el | 2 +- matlab.el | 128 ++++++++++++++++++++---------------------- tests/continuations.m | 70 +++++++++++++++++++++++ tests/indents.m | 6 -- tests/metest.el | 2 +- 6 files changed, 191 insertions(+), 87 deletions(-) create mode 100644 tests/continuations.m diff --git a/matlab-scan.el b/matlab-scan.el index 525478f..32e09c8 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -150,18 +150,19 @@ LEVEL of 1 is the most primitive / simplest data. LEVEL of 2 is stuff that is derived from previous lines of code. This function caches computed context onto the line it is for, and will return the cache if it finds it." - (cond ((= level 1) - (let ((ctxt (save-excursion - (back-to-indentation) - (matlab-scan-cache-get)))) - (unless ctxt - (setq ctxt (matlab-compute-line-context-lvl-1)) - (matlab-scan-cache-put ctxt)) - ctxt)) - ((= level 2) - (apply 'matlab-compute-line-context-lvl-2 context)) - (t - nil)) + (save-match-data + (cond ((= level 1) + (let ((ctxt (save-excursion + (back-to-indentation) + (matlab-scan-cache-get)))) + (unless ctxt + (setq ctxt (matlab-compute-line-context-lvl-1)) + (matlab-scan-cache-put ctxt)) + ctxt)) + ((= level 2) + (apply 'matlab-compute-line-context-lvl-2 context)) + (t + nil))) ) ;;; LEVEL 1 SCANNER @@ -448,7 +449,7 @@ If the line starts with a comment return nil, otherwise t." (nth mlf-paren-outer-char lvl1)) (defsubst matlab-line-close-paren-outer-point (lvl1) - "The poit the outermost parenthetical expression start is at." + "The point the outermost parenthetical expression start is at." (nth mlf-paren-outer-point lvl1)) ;;; LEVEL 2 SCANNER @@ -777,6 +778,49 @@ backward over lines that include ellipsis." (back-to-indentation) (point)))) +(defun matlab-scan-end-of-command (&optional lvl1) + "Return point in buffer at the end of this command. +This function walks down past continuations and open arrays." + (unless lvl1 (setq lvl1 (matlab-compute-line-context 1))) + ;; If we are in a block comment, just jump to the end, and + ;; that's it. + (let ((bcs (matlab-line-block-comment-start lvl1))) + (if bcs + (progn + (goto-char bcs) + (forward-comment 1) + (setq lvl1 (matlab-compute-line-context 1))) + + (let ((done nil) + (lvlwalk lvl1)) + (while (not done) + (end-of-line) + (cond ((matlab-end-of-outer-list) + ;; If we are in a list, this moves to the end and + ;; returns non-nil. We are now where we want to be + ;; so nothing to do. + nil) + + ((matlab-line-ellipsis-p lvlwalk) + ;; This is a continuation, keep going. + (forward-line 1)) + + (t + ;; None of these conditions were true, so + ;; we must be done! + (setq done t)) + ) + + ;; Protect against travelling too far. + (when (eobp) (setq done t)) + ;; Scan the next line. + (when (not done) (setq lvlwalk (matlab-compute-line-context 1))) + )) + + ;; Return where we ended up + (end-of-line) + (point-at-eol)))) + ;;; Caching ;; (defvar matlab-scan-temporal-cache nil diff --git a/matlab-syntax.el b/matlab-syntax.el index d852fe8..c664185 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -518,7 +518,7 @@ Returns non-nil if the cursor moved." (when (nth 9 pps) (goto-char (car (nth 9 pps))) ))) (defun matlab-end-of-outer-list () - "If the cursor is in a, move to the end of the outermost list.. + "If the cursor is in a list, move to the end of the outermost list.. Returns non-nil if the cursor moved." (let* ((pps (syntax-ppss (point)))) (when (nth 9 pps) diff --git a/matlab.el b/matlab.el index 0944758..c3146e6 100644 --- a/matlab.el +++ b/matlab.el @@ -1440,7 +1440,8 @@ that command. Return t on success, nil if we couldn't navigate backwards." (let ((ans (matlab-find-prev-line 'ignore-comments))) (when ans - (matlab-beginning-of-command) + (matlab-scan-beginning-of-command) + ;(matlab-beginning-of-command) ans))) (defun matlab-find-prev-line (&optional ignorecomments) @@ -1493,11 +1494,8 @@ restricted." (declare (indent 0) (debug t)) `(save-restriction (when (not matlab-in-command-restriction) - (narrow-to-region (save-excursion - (matlab-beginning-of-command) - (beginning-of-line) - (point)) - (1+ (matlab-point-at-eol)))) + (narrow-to-region (matlab-scan-beginning-of-command) + (matlab-scan-end-of-command))) (let ((matlab-in-command-restriction t)) ,@forms ))) @@ -1820,44 +1818,46 @@ Use this if you know what context you're in." (save-match-data (save-restriction (widen) - (cond - ((not (looking-at (matlab-block-beg-re))) - ;; Not looking at a valid block - nil) - ((eq (preceding-char) ?.) - ;; Any block preceeded by a '.' is a field in a struct, and not valid. - nil) - ;; Else, are we on a block that has special syntax? - ((not (looking-at matlab-innerblock-syntax-re)) - ;; Not an innerblock syntax that only work withing special blocks - ;; thus automatically true. - t) + (let ((foundblock (and (looking-at (matlab-block-beg-re)) + (match-string-no-properties 1)))) + (cond + ((not foundblock) + ;; Not looking at a valid block + nil) + ((eq (preceding-char) ?.) + ;; Any block preceeded by a '.' is a field in a struct, and not valid. + nil) + ;; Else, are we on a block that has special syntax? + ((not (looking-at matlab-innerblock-syntax-re)) + ;; Not an innerblock syntax that only work withing special blocks + ;; thus automatically true. + t) - ;; Else, a special block keyword. We need to check the context of this - ;; block to know if this innerblock is valid. + ;; Else, a special block keyword. We need to check the context of this + ;; block to know if this innerblock is valid. - ;; Cheap check - if functions don't have end, then always invalid - ;; since these context blocks can't exist. - ((not matlab-functions-have-end) - nil) + ;; Cheap check - if functions don't have end, then always invalid + ;; since these context blocks can't exist. + ((not matlab-functions-have-end) + nil) - ;; Cheap check - is this block keyword not the first word for this command? - ((save-excursion (skip-syntax-backward " ") (not (bolp))) - ;; Not first on line, not valid block. - ;; Technically it COULD be valid, but we need some cheap ways - ;; to skip over some types of syntaxes that look dumb. - nil) - - ;; Cheap check - is this at the beginning of a command line (ignore ... ) - ((not (eq (point) (save-excursion (matlab-beginning-of-command) (point)))) - ;; If this statement is not also the first word on this command - ;; then it can't be one of these features. - nil) + ;; Cheap check - is this block keyword not the first word for this command? + ((save-excursion (skip-syntax-backward " ") (not (bolp))) + ;; Not first on line, not valid block. + ;; Technically it COULD be valid, but we need some cheap ways + ;; to skip over some types of syntaxes that look dumb. + nil) - ;; Expensive check - is this block in the right context? - ((or matlab-valid-block-start-slow-and-careful known-parent-block) + ;; Cheap check - is this at the beginning of a command line (ignore ... ) + ((not (eq (save-excursion (back-to-indentation) (point)) + (save-excursion (matlab-scan-beginning-of-command) (point)))) + ;; If this statement is not also the first word on this command + ;; then it can't be one of these features. + nil) + + ;; Expensive check - is this block in the right context? + ((or matlab-valid-block-start-slow-and-careful known-parent-block) - (let ((foundblock (match-string-no-properties 1))) (cond ((string= foundblock "arguments") ;; Argument is only valid if it is the FIRST thing in a funtion. @@ -1915,19 +1915,19 @@ Use this if you know what context you're in." ;; All else fails - so not valid. (t nil) - ))) ;; End slow-and-careful + )) ;; End slow-and-careful - ;; A cheap version of the expensive check - ((and (not matlab-valid-block-start-slow-and-careful) - (looking-at matlab-innerblock-syntax-re)) - ;; If we are not slow and careful, we just need to return t if we see - ;; one of these keywords since other cases where these weren't 1st on the line - ;; or not in a classdef file are already filtered out. - t) + ;; A cheap version of the expensive check + ((and (not matlab-valid-block-start-slow-and-careful) + (looking-at matlab-innerblock-syntax-re)) + ;; If we are not slow and careful, we just need to return t if we see + ;; one of these keywords since other cases where these weren't 1st on the line + ;; or not in a classdef file are already filtered out. + t) - ;; If none of the valid cases, must be invalid - (t nil) - )))) + ;; If none of the valid cases, must be invalid + (t nil) + ))))) (defun matlab-previous-line-belongs-to-classdef-p () "Return the nature of the line of code before this one. @@ -2033,8 +2033,10 @@ If `matlab-functions-have-end', skip over functions with end." "Go to the beginning of an M command. Travels across continuations." (interactive "P") - (if arg + (if t (matlab-scan-beginning-of-command) + + ;;; TODO - delete this old impl (beginning-of-line) (save-match-data (let* ((lvl1 (matlab-compute-line-context 1)) @@ -2298,31 +2300,25 @@ special items." nil))) (defun matlab-lattr-block-close (&optional start) - "Return the number of closing block constructs. + "Return the number of closing block constructs on this line. Argument START is where to start searching from." - (let ((v 0) - (lvl1 (matlab-compute-line-context 1))) - (save-excursion - (when start (goto-char start)) + (save-excursion + (when start (goto-char start)) + (let ((v 0) + (lvl1 (matlab-compute-line-context 1))) (if (matlab-line-comment-p lvl1) ;; If this is even vagely a comment line, then there is no ;; need to do any scanning. 0 ;; Else, lets scan. (matlab-with-current-command - ;; We used to do this, but now... - ;;(goto-char (point-at-eol)) ;; lets only scan from the beginning of the comment + (goto-char start) (matlab-line-end-of-code lvl1) - ;; If in a comment, move out of it first. - (when (matlab-beginning-of-string-or-comment) - ;; in case of no space between comment and end, need to move back - ;; over the comment char for next search to work. - ;;(forward-char 1) - ) - - ;; Count every END in the line, skipping over active blocks + ;; Count every END from our starting point till the beginning of the + ;; command. That count indicates unindent from the beginning of the + ;; command which anchors the starting indent. (while (re-search-backward (concat "\\<" (matlab-block-end-re) "\\>") nil t) (let ((startmove (match-end 0)) diff --git a/tests/continuations.m b/tests/continuations.m new file mode 100644 index 0000000..1c31191 --- /dev/null +++ b/tests/continuations.m @@ -0,0 +1,70 @@ +function continuations(a,b) %!!0 +% !!0 Help Comment + + arguments (Repeating) + a (1,1) ... % !!8 + double % !!12 + b (1,1) double { mustBeSomething ... %!!8 + mustBeSomethingElse } %!!25 + end + + global var1, ... % !!4 + var2 % !!8 + + localfcn(a,b); % !!4 + + localfcn(var1, ... % !!4 + var2); % !!13 + + localfcn(a, localfcn(b, localfcn(var1,... %!!4 + var2)... %!37 + )... %!!24 + ); %!!12 + + + code1(), ... + code2(); %!!8 + + % NOTE: Blank space below should cancel the indent effect of ellipsis. + code1() ... + + var1 = 1 + ... %!!4 + ... %!!11 + 2 + ... %!!11 + 3; %!!11 + + medvar = 1 + ... %!!4 + 2; %!!13 + + long_var_name = 1 + ... %!!4 + 2; %!!8 + + localfcn (medvar, ... %!!4 + long_var_name); %!!19 + + localfcn( ... %!!4 + a,b); %!!8 + + + if true, if true, if true %!!4 + localfcn(a,b) ... %!!16 + end; ... %!!12 + end; ... %!!8 + end ... %!!4 + + code1(); %!!4 - this after those continued ends + + + + ... % Continuation by itself just before an end. +end %!!0 + + +function val = localfcn(c,d) %!!0 +% !!0 Help Comment + + + + val = c+d; % !!4 + +end %!!0 \ No newline at end of file diff --git a/tests/indents.m b/tests/indents.m index a25d910..ef5d33b 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -100,12 +100,6 @@ function B = ends_in_comments_and_strings() end ... the other end % !! 4 - code1(), ... - code2(); %!!8 - - % NOTE: Blank space below should cancel the indent effect of ellipsis. - code1() ... - B = [ B A ]; % !!4 str = 'This is a char array with ... in it'; diff --git a/tests/metest.el b/tests/metest.el index 4525f14..d79b099 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -245,7 +245,7 @@ (list cnt "test"))) -(defvar met-indents-files '("indents.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m") +(defvar met-indents-files '("indents.m" "continuations.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m") "List of files for running syntactic indentation tests.") (defun metest-indents-randomize-files () From ec7421cb58ca7f0704c5d17a083f8e4f5e81db67 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 19 Mar 2021 20:02:58 -0400 Subject: [PATCH 077/168] matlab-scan.el: Add support for block navigation. (matlab-on-keyword-p): New (matlab-kwt-indent, mtlab-kwt-blocks): New regex caches (matlab-keyword-regex): Update interface to be simpler. Support more caches for regex. (matlab-compute-line-context-lvl-1): Update to use new `matlab-on-keyword-p' feature. (matlab--buffer-block-tree): New (but not yet used) (matlab--mk-keyword-node, matlab--known-parent-block): New (matlab--valid-keyword-point, matlab--valid-arguments-keyword-point): New (matlab--scan-derive-block-state): New (matlab--scan-block-forward, matlab--scan-block-forward-up): New (matlab--scan-block-backward, matlab--scan-block-backward-up): New (matlab--re-search-keyword-forward, -backward): Moved. matlab-syntax.el: (matlab-beginning-of-string-or-comment): Add all-comments optional input. (matlab-end-of-string-or-comment): Add all-comments optional input. (matlab-syntax-keyword-as-variable-p): New (matlab-valid-keyword-syntax): New --- matlab-scan.el | 365 ++++++++++++++++++++++++++++++++++++++--------- matlab-syntax.el | 35 ++++- 2 files changed, 325 insertions(+), 75 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 32e09c8..bc43d33 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -29,8 +29,11 @@ ;;; Code: + ;;; Keyword and REGEX constants ;; +;; List of our keywords, and tools to look up keywords and find out +;; what they are. (defconst matlab-block-keyword-list '(("end" . end) ("function" . decl) ("classdef" . decl) @@ -76,23 +79,43 @@ If word is a number, it is a match-string index for the current buffer." (sym (intern-soft local-word matlab-keyword-table))) (and sym (symbol-value sym)))) +(defsubst matlab-on-keyword-p () + "Return the type of keyword under point, or nil." + (when (matlab-valid-keyword-syntax) + ;; Not in invalid context, look it up. + (matlab-keyword-p (buffer-substring-no-properties + (save-excursion (skip-syntax-backward "w_") (point)) + (save-excursion (skip-syntax-forward "w_") (point)))))) (defvar matlab-kwt-all nil) (defvar matlab-kwt-decl nil) +(defvar matlab-kwt-indent nil) (defvar matlab-kwt-end nil) +(defvar matlab-kwt-blocks nil) (defun matlab-keyword-regex (types) "Find keywords that match TYPES and return optimized regexp. +TYPES can also be a single symbol that represents a common list of +keyword types. These include: + all - any kind of keyword + decl - declarations, like class or function + indent - any keyword that causes an indent + end - the end keyword (that causes dedent) + blocks - indent and end keywords Caches some found regexp to retrieve them faster." (cond ((or (eq types nil) (eq types 'all)) (or matlab-kwt-all (setq matlab-kwt-all (matlab--keyword-regex nil)))) - ((equal types '(decl)) - (or matlab-kwt-decl (setq matlab-kwt-decl (matlab--keyword-regex types)))) - ((equal types '(end)) - (or matlab-kwt-end (setq matlab-kwt-end (matlab--keyword-regex types)))) + ((eq types 'decl) + (or matlab-kwt-decl (setq matlab-kwt-decl (matlab--keyword-regex '(decl))))) + ((eq types 'indent) + (or matlab-kwt-indent (setq matlab-kwt-indent (matlab--keyword-regex '(decl ctrl args mcos))))) + ((eq types 'end) + (or matlab-kwt-end (setq matlab-kwt-end (matlab--keyword-regex '(end))))) + ((eq types 'blocks) + (or matlab-kwt-blocks (setq matlab-kwt-blocks (matlab--keyword-regex '(end decl ctrl args mcos))))) (t - (setq matlab-kwt-all (matlab--keyword-regex types))))) + (matlab--keyword-regex types)))) (defun matlab--keyword-regex (types) "Find keywords that match TYPES and return an optimized regexp." @@ -101,48 +124,13 @@ Caches some found regexp to retrieve them faster." matlab-block-keyword-list) (regexp-opt lst 'symbols))) -;;; Searching for keywords -;; -;; These utilities will simplify searching for code bits by skipping -;; anything in a comment or string. -(defun matlab-re-search-keyword-forward (regexp &optional bound noerror) - "Like `re-search-forward' but will not match content in strings or comments." - (let ((ans nil)) - (save-excursion - (while (and (not ans) - (setq ans (re-search-forward regexp bound noerror))) - (cond ((matlab-end-of-string-or-comment) - ;; We landed in a string this time through, so clear - ;; the answer and skip the rest of it. - ;; TODO: can it skip over all adjacent comments too? - (setq ans nil)) - ((matlab-end-of-outer-list) - (setq ans nil)) - ))) - (when ans (goto-char ans)))) - -(defun matlab-re-search-keyword-backward (regexp &optional bound noerror) - "Like `re-search-backward' but will not match content in strings or comments." - (let ((ans nil)) - (save-excursion - (while (and (not ans) - (setq ans (re-search-backward regexp bound noerror))) - (cond ((matlab-beginning-of-string-or-comment) - ;; We landed in a string this time through, so clear - ;; the answer and skip the rest of it. - (setq ans nil)) - ((matlab-beginning-of-outer-list) - (setq ans nil)) - ))) - (when ans (goto-char ans)))) - + ;;; Context Parsing ;; ;; Find some fast ways to identify the context for a given line. ;; Use tricks to derive multiple pieces of information and do lookups ;; as quickly as possible. - (defun matlab-compute-line-context (level &rest context) "Compute and return the line context for the current line of MATLAB code. LEVEL indicates how much information to return. @@ -207,6 +195,7 @@ in a single call using fastest methods." (ec-type nil) (ec-col nil) (cont-from-prev nil) + (symval nil) ) ;; This means we are somewhere inside a cell, array, or arg list. @@ -253,34 +242,25 @@ in a single call using fastest methods." ;; Looking at word constituent. If so, identify if it is one of our ;; special identifiers. - ((looking-at "\\w+\\>") - (if (/= paren-depth 0) - (setq ltype 'code) - - ;; If not in parens, this might be a keyword. - ;; Look up our various keywords. - (let* ((symval (matlab-keyword-p 0))) - (if symval - (cond - ;; Special end keyword is in a class all it's own - ((eq symval 'end) - (setq ltype 'end)) - ;; If we found this in our keyword table, then it is a start - ;; of a block with a subtype. - ((memq symval '(decl args mcos ctrl mid case)) - (setq ltype 'block-start - stype symval)) - ;; Some keywords aren't related to blocks with indentation - ;; controls. Those are treated as code, with a type. - ((memq symval '(keyword vardecl)) - (setq ltype 'code - stype symval)) - ;; Else - not a sym - just some random code. - (t - (setq ltype 'code))) - (setq ltype 'code)) - ))) + ((and (= paren-depth 0) + (setq symval (matlab-on-keyword-p))) + (cond + ;; Special end keyword is in a class all it's own + ((eq symval 'end) + (setq ltype 'end)) + ;; If we found this in our keyword table, then it is a start + ;; of a block with a subtype. + ((memq symval '(decl args mcos ctrl mid case)) + (setq ltype 'block-start + stype symval)) + ;; Some keywords aren't related to blocks with indentation + ;; controls. Those are treated as code, with a type. + (t + (setq ltype 'code + stype symval)) + )) + ;; Looking at a close paren. ((and (< 0 paren-depth) (looking-at "\\s)")) (setq ltype 'close-paren)) @@ -451,6 +431,7 @@ If the line starts with a comment return nil, otherwise t." (defsubst matlab-line-close-paren-outer-point (lvl1) "The point the outermost parenthetical expression start is at." (nth mlf-paren-outer-point lvl1)) + ;;; LEVEL 2 SCANNER ;; @@ -700,6 +681,7 @@ If LVL2 is nil, compute it." "Return the location of an opening paren if in array parens." (matlab-line-close-paren-outer-point (matlab-get-lvl1-from-lvl2 lvl2))) + ;;; Scanning Accessor utilities ;; ;; Some utilities require some level of buffer scanning to get the answer. @@ -821,6 +803,247 @@ This function walks down past continuations and open arrays." (end-of-line) (point-at-eol)))) + +;;; BLOCK SCANNING and SEARCHING +;; +;; Focused scanning across block structures, like if / else / end. + +(defvar matlab--buffer-block-tree nil + "A Tree data structure representing what we know about this buffer. +The block tree is a cache of known block structures, like function, if, +else, and ends. Scanning forward through a buffer is faster than +backward, so this tree tracks what we know 'so far' in a buffer to speed +up some types of keyword navigation and lookup.") +(make-variable-buffer-local 'matlab--buffer-block-tree) + +(defsubst matlab--mk-keyword-node () + "Like `matlab-on-keyword-p', but returns a node for block scanning. +The elements of the return node are: + 0 - type of keyword, like ctrl or decl + 1 - text of the keyword + 2 - buffer pos for start of keyword" + ;; Don't check the context - assume our callers have vetted this + ;; point. This is b/c the search fcns already skip comments and + ;; strings for efficiency. + (let* ((start (save-excursion (skip-syntax-backward "w_") (point))) + (txt (buffer-substring-no-properties + start + (save-excursion (skip-syntax-forward "w_") (point)))) + (type (matlab-keyword-p txt))) + (when type (list type txt start) ))) + +;;; Valid keyword locations +;; +(defsubst matlab--valid-keyword-point () + "Return non-nil if point is valid for keyword. +Returns nil for failing `matlab-valid-keyword-syntax'. +Returns nil if preceeding non-whitespace char is `.'" + (and (matlab-valid-keyword-syntax) + (not (matlab-syntax-keyword-as-variable-p)))) + +(defsubst matlab--known-parent-block(parent) + "Return PARENT if it is a known parent block." + (if (or (not parent) (eq (car parent) 'unknown)) + nil + parent)) + +(defun matlab--valid-mcos-keyword-point (&optional parentblock) + "Return non-nil if at a location that is valid for MCOS keywords. +This means that the parent block is a classdef. +Optional input PARENTBLOCK is a precomputed keyword node +representing the current block context point is in. +Assume basic keyword checks have already been done." + ;; If a parent was provided, use that. + (if (matlab--known-parent-block parentblock) + (string= (nth 1 parentblock) "classdef") + + (and (eq matlab-functions-have-end 'class) ;; not a class, no mcos allowed. + (save-excursion (skip-syntax-backward "w") + (skip-syntax-backward " ") + (bolp)) ;; Must be first on line. + + ;; Otherwise, roll back a single command. in MUST be + ;; an END indent 4 or CLASSDEF + (save-excursion + (skip-syntax-backward "w") + (forward-comment -100000) + (back-to-indentation) ;; TODO -> this should be beginning-of-command + (let ((prev (matlab--mk-keyword-node))) + (or (string= (nth 1 prev) "classdef") + (and (string= (nth 1 prev) "end") + (= (current-indentation) matlab-indent-level)))))))) + +(defun matlab--valid-arguments-keyword-point (&optional parentblock) + "Return non-nil if at a location that is valid for ARGUMENTS keyword. +This means that the parent block is a function, and this is first cmd in +the function. +Optional input PARENTBLOCK is a precomputed keyword node +representing the current block context point is in. +Assume basic keyword checks have already been done." + (let ((parent + (or (matlab--known-parent-block parentblock) ;; technically this can lie, but it's fast. + (save-excursion (skip-syntax-backward "w") + (forward-comment -100000) + (matlab-scan-beginning-of-command) + (and (matlab--valid-keyword-point) + (matlab--mk-keyword-node)))))) + (string= (nth 1 parent) "function"))) + +(defun matlab--scan-derive-block-state (providedstate filter) + "Return a block state for current point. +If PROVIDEDSTATE is non nil, use that. +Return nil if no valid block under pt." + (or providedstate + (let ((thiskeyword (matlab--mk-keyword-node))) + (if (or (not thiskeyword) + (not (matlab--valid-keyword-point)) + (not (memq (car thiskeyword) filter)) + ) + nil + (push thiskeyword providedstate))))) + +;;; Block Scanning +;; +(defun matlab--scan-block-forward (&optional bounds state) + "Scan forward over 1 MATLAB block construct. +Return current state on exit. + nil - success + non-nil - indicates incomplete scanning +Also skips over all nexted block constructs along the way. +Assumes cursor is in a valid starting state, otherwise ERROR. +If cursor is on a middle-block construct like else, case, ERROR. + +Optional BOUNDS is a point in the buffer past which we won't scan. +Optional STATE is the current parsing state to start from. +Use STATE to stop/start block scanning partway through." + (let ((blockstate (matlab--scan-derive-block-state state '(decl args mcos ctrl))) + (thiskeyword nil) + (stop nil) + (regex (matlab-keyword-regex 'blocks)) + ) + + (when (not blockstate) (error "Not on valid block start.")) + + (when (not state) (skip-syntax-forward "w")) ;; skip keyword + + (while (and blockstate (not stop)) + (if (not (setq thiskeyword (matlab-re-search-keyword-forward regex bounds t))) + (setq stop t) + (cond ((eq (car thiskeyword) 'end) + ;; On end, pop last start we pushed + (pop blockstate)) + ((eq (car thiskeyword) 'mcos) + (if (matlab--valid-mcos-keyword-point (car blockstate)) + (push thiskeyword blockstate) + ;; else, just skip it + )) + ((eq (car thiskeyword) 'args) + (if (matlab--valid-arguments-keyword-point (car blockstate)) + (push thiskeyword blockstate) + ;; else, just skip it, not a keyword + )) + (t + (push thiskeyword blockstate))) + )) + blockstate)) + +(defun matlab--scan-block-forward-up (&optional bounds) + "Like `matlab--scan-block-forward', but cursor is not on a keyword. +Instead, travel to end as if on keyword." + (let ((currentstate '((unknown "" 0)))) + (matlab--scan-block-forward bounds currentstate))) + + +(defun matlab--scan-block-backward (&optional bounds state) + "Scan forward over 1 MATLAB block construct. +Return current state on exit. + nil - success + non-nil - indicates incomplete scanning +Also skips over all nexted block constructs along the way. +Assumes cursor is in a valid starting state, otherwise ERROR. +If cursor is on a middle-block construct like else, case, ERROR. + +Optional BOUNDS is a point in the buffer past which we won't scan. +Optional STATE is the current parsing state to start from. +Use STATE to stop/start block scanning partway through." + (let ((blockstate (matlab--scan-derive-block-state state '(end))) + (thiskeyword nil) + (stop nil) + (regex (matlab-keyword-regex 'blocks)) + ) + + (when (not blockstate) (error "Not on valid block end.")) + + (when (not state) (skip-syntax-backward "w")) ;; skip keyword + + (while (and blockstate (not stop)) + (if (not (setq thiskeyword (matlab-re-search-keyword-backward regex bounds t))) + (setq stop t) + (cond ((eq (car thiskeyword) 'end) + ;; On end, push this keyword + (push thiskeyword blockstate)) + ((eq (car thiskeyword) 'mcos) + (if (matlab--valid-mcos-keyword-point nil) + (pop blockstate) + ;; else, just skip it + )) + ((eq (car thiskeyword) 'args) + (if (matlab--valid-arguments-keyword-point nil) + (pop blockstate) + ;; else, just skip it, not a keyword + )) + (t + (pop blockstate))) + )) + blockstate)) + +(defun matlab--scan-block-backward-up (&optional bounds) + "Like `matlab--scan-block-forward', but cursor is not on a keyword. +Instead, travel to end as if on keyword." + (let ((currentstate '((end "end" 0)))) + (matlab--scan-block-backward bounds currentstate))) + +;;; Searching for keywords +;; +;; These utilities will simplify searching for code bits by skipping +;; anything in a comment or string. +(defun matlab-re-search-keyword-forward (regexp &optional bound noerror) + "Like `re-search-forward' but will not match content in strings or comments." + (let ((ans nil) (case-fold-search nil)) + (save-excursion + (while (and (not ans) + (setq ans (re-search-forward regexp bound noerror))) + ;; Check for simple cases that are invalid for keywords + ;; for strings, comments, and lists, skip to the end of them + ;; to not waste time searching for keywords inside. + (cond ((matlab-end-of-string-or-comment t) + (setq ans nil)) + ((matlab-end-of-outer-list) + (setq ans nil)) + ((matlab-syntax-keyword-as-variable-p) + (setq ans nil)) + ))) + (when ans (goto-char ans) (matlab--mk-keyword-node)))) + +(defun matlab-re-search-keyword-backward (regexp &optional bound noerror) + "Like `re-search-backward' but will not match content in strings or comments." + (let ((ans nil) (case-fold-search nil)) + (save-excursion + (while (and (not ans) + (setq ans (re-search-backward regexp bound noerror))) + ;; Check for simple cases that are invalid for keywords + ;; for strings, comments, and lists, skip to the end of them + ;; to not waste time searching for keywords inside. + (cond ((matlab-beginning-of-string-or-comment t) + (setq ans nil)) + ((matlab-beginning-of-outer-list) + (setq ans nil)) + ((matlab-syntax-keyword-as-variable-p) + (setq ans nil)) + ))) + (when ans (goto-char ans) (matlab--mk-keyword-node)))) + + ;;; Caching ;; (defvar matlab-scan-temporal-cache nil @@ -871,13 +1094,15 @@ Make sure the cache doesn't exceed max size." (remove-hook 'before-change-functions 'matlab-scan-before-change-fcn t) (setq matlab-scan-temporal-cache nil)) - + ;;; Debugging and Querying ;; (defvar matlab-scan-cache-stats nil "Cache stats for tracking effectiveness of the cache.") (defun matlab-scan-stat-reset (&optional arg) - "Reset the stats cache." + "Reset the stats cache. +With no arg, disable gathering stats. +With arg, enable gathering stats, and flush old stats." (interactive "P") (if arg (progn (setq matlab-scan-cache-stats nil) diff --git a/matlab-syntax.el b/matlab-syntax.el index c664185..d1bea60 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -417,14 +417,19 @@ bounds of the string or comment the cursor is in" ;; Return the syntax syntax)) -(defsubst matlab-beginning-of-string-or-comment () +(defsubst matlab-beginning-of-string-or-comment (&optional all-comments) "If the cursor is in a string or comment, move to the beginning. Returns non-nil if the cursor moved." (let* ((pps (syntax-ppss (point)))) - (when (nth 8 pps) (goto-char (nth 8 pps)) ))) + (when (nth 8 pps) + (goto-char (nth 8 pps)) + (when all-comments (forward-comment -100000)) + t))) -(defun matlab-end-of-string-or-comment () +(defsubst matlab-end-of-string-or-comment (&optional all-comments) "If the cursor is in a string or comment, move to the end. +If optional ALL-COMMENTS is non-nil, then also move over all +adjacent comments. Returns non-nil if the cursor moved." (let* ((pps (syntax-ppss (point)))) (when (nth 8 pps) @@ -433,8 +438,8 @@ Returns non-nil if the cursor moved." (goto-char (nth 8 pps)) (if (nth 3 pps) (goto-char (scan-sexps (point) 1)) - (forward-comment 1)) - ))) + (forward-comment (if all-comments 100000 1))) + t))) ;;; Block Comment handling ;; @@ -528,6 +533,26 @@ Returns non-nil if the cursor moved." (goto-char (scan-sexps (point) 1)) ))) +;;; Useful checks for state around point. +;; +(defsubst matlab-syntax-keyword-as-variable-p () + "Return non-nil if the current word is treated like a variable. +This could mean it is: + * Field of a structure + * Assigned from or into with =" + (or (save-excursion (skip-syntax-backward "w") + (skip-syntax-backward " ") + (or (= (preceding-char) ?\.) + (= (preceding-char) ?=))) + (save-excursion (skip-syntax-forward "w") + (skip-syntax-forward " ") + (= (following-char) ?=)))) + +(defsubst matlab-valid-keyword-syntax () + "Return non-nil if cursor is not in a string, comment, or parens." + (let ((pps (syntax-ppss (point)))) + (not (or (nth 8 pps) (nth 9 pps))))) ;; 8 == string/comment, 9 == parens + ;;; Syntax Compat functions ;; ;; Left over old APIs. Delete these someday. From 81cc7718eb074d01e9e3527d4012380331b71d1c Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 19 Mar 2021 23:28:51 -0400 Subject: [PATCH 078/168] matlab-scane.el: (matlab--buffer-block-tree): Delete (matlab--scan-block-forward): Handle case when `matlab-functions-have-end' is false. (matlab-re-search-keyword-forward,matlab-re-search-keyword-backward): Handle bounds correctly before restarting a scan. (matlab-scan-before-change-fcn): Only clear cached items AFTER the change. matlab.el: (matlab-in-command-restriction, matlab-with-current-command) (matlab-lattr-local-end): DELETE (matlab-backward-sexp, matlab-forward-sexp): Reimplement using matlab--scan-block-forward/backward. (matlab-lattr-block-cont, matlab-lattr-block-close): Remove narrowing to current command, and use new search utils with bounds. (matlab-lattr-middle-block-cont): Comment out tests/metest.m: (metest-sexp-traversal-test variable): better name. (metest-sexp-traversal-test): Use new `matlab--scan-block-forward' / backward instead of orig fcns. tests/blocks.m: Tweak a few test points to make them more challenging. tests/continuations.m: Added test with continuation, but disabled the problem. tests/indents.m: Brought in problem lines that break array sexp w/ ' in continuation tests/mfuncnoend.m: Fix whitespace. tests/mfuncnoendblock.m: Fix mlint warnings. --- matlab-scan.el | 46 ++++- matlab.el | 400 +++++++++++++++++----------------------- tests/blocks.m | 16 +- tests/continuations.m | 9 + tests/indents.m | 13 +- tests/metest.el | 25 +-- tests/mfuncnoend.m | 10 +- tests/mfuncnoendblock.m | 1 + 8 files changed, 253 insertions(+), 267 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index bc43d33..fbd9670 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -808,14 +808,6 @@ This function walks down past continuations and open arrays." ;; ;; Focused scanning across block structures, like if / else / end. -(defvar matlab--buffer-block-tree nil - "A Tree data structure representing what we know about this buffer. -The block tree is a cache of known block structures, like function, if, -else, and ends. Scanning forward through a buffer is faster than -backward, so this tree tracks what we know 'so far' in a buffer to speed -up some types of keyword navigation and lookup.") -(make-variable-buffer-local 'matlab--buffer-block-tree) - (defsubst matlab--mk-keyword-node () "Like `matlab-on-keyword-p', but returns a node for block scanning. The elements of the return node are: @@ -928,7 +920,12 @@ Use STATE to stop/start block scanning partway through." (while (and blockstate (not stop)) (if (not (setq thiskeyword (matlab-re-search-keyword-forward regex bounds t))) - (setq stop t) + (progn + (setq stop t) + (when (and (not matlab-functions-have-end) + (eq (car (car blockstate)) 'decl)) + (goto-char (point-max)) + (pop blockstate))) (cond ((eq (car thiskeyword) 'end) ;; On end, pop last start we pushed (pop blockstate)) @@ -942,6 +939,15 @@ Use STATE to stop/start block scanning partway through." (push thiskeyword blockstate) ;; else, just skip it, not a keyword )) + ((and (not matlab-functions-have-end) + (eq (car thiskeyword) 'decl) + (eq (car (car blockstate)) 'decl) + ) + ;; No ends on functions - in this case we need treat a function as an end. + ;; b/c you can't have nested functions, but only if the thing we try to match + ;; it to is another fcn. + ;; This POP should result in empty state. + (pop blockstate)) (t (push thiskeyword blockstate))) )) @@ -1012,6 +1018,7 @@ Instead, travel to end as if on keyword." (let ((ans nil) (case-fold-search nil)) (save-excursion (while (and (not ans) + (or (not bound) (< (point) bound)) (setq ans (re-search-forward regexp bound noerror))) ;; Check for simple cases that are invalid for keywords ;; for strings, comments, and lists, skip to the end of them @@ -1030,6 +1037,7 @@ Instead, travel to end as if on keyword." (let ((ans nil) (case-fold-search nil)) (save-excursion (while (and (not ans) + (or (not bound) (> (point) bound)) (setq ans (re-search-backward regexp bound noerror))) ;; Check for simple cases that are invalid for keywords ;; for strings, comments, and lists, skip to the end of them @@ -1080,7 +1088,25 @@ Make sure the cache doesn't exceed max size." (defun matlab-scan-before-change-fcn (start end &optional length) "Function run in after change hooks." - (setq matlab-scan-temporal-cache nil)) + ;;(setq matlab-scan-temporal-cache nil)) + (let ((pt (point)) + (cache matlab-scan-temporal-cache) + (newcache nil)) + ;; Flush whole lines. + (save-excursion + (goto-char start) + (setq start (point-at-bol))) + ;; Only drop items AFTER the start of our region. + (while cache + (if (<= start (matlab-line-point (car cache))) + (matlab-scan-stat-inc 'flushskip) + (push (car cache) newcache) + (matlab-scan-stat-inc 'flush)) + (setq cache (cdr cache))) + ;;(setq newcache nil) + ;;(when (and newcache (symbolp (car newcache))) (debug)) + (setq matlab-scan-temporal-cache newcache) + )) (defun matlab-scan-setup () "Setup use of the indent cache for the current buffer." diff --git a/matlab.el b/matlab.el index c3146e6..509117a 100644 --- a/matlab.el +++ b/matlab.el @@ -1483,22 +1483,22 @@ Ignore comments and whitespace." ;; t)) -(defvar matlab-in-command-restriction nil - "Non-nil if currently in a `matlab-with-current-command' form.") - -(defmacro matlab-with-current-command (&rest forms) - "Restrict region to the current command and run FORMS. -Restore restriction after FORMS run. -This command will not add a restriction if we are already -restricted." - (declare (indent 0) (debug t)) - `(save-restriction - (when (not matlab-in-command-restriction) - (narrow-to-region (matlab-scan-beginning-of-command) - (matlab-scan-end-of-command))) - (let ((matlab-in-command-restriction t)) - ,@forms - ))) +;;(defvar matlab-in-command-restriction nil +;; "Non-nil if currently in a `matlab-with-current-command' form.") +;; +;;(defmacro omatlab-with-current-command (&rest forms) +;; "Restrict region to the current command and run FORMS. +;;Restore restriction after FORMS run. +;;This command will not add a restriction if we are already +;;restricted." +;; (declare (indent 0) (debug t)) +;; `(save-restriction +;; (when (not matlab-in-command-restriction) +;; (narrow-to-region (matlab-scan-beginning-of-command) +;; (matlab-scan-end-of-command))) +;; (let ((matlab-in-command-restriction t)) +;; ,@forms +;; ))) (defun matlab-valid-end-construct-p () "Return non-nil if the end after point terminates a block. @@ -1669,69 +1669,53 @@ If optional AUTOEND, then pretend we are at an end. If optional NOERROR, then we return t on success, and nil on failure. This assumes that expressions do not cross \"function\" at the left margin." (interactive "P") - (matlab-navigation-syntax - (skip-chars-backward " \t\n") - (cond - ;; Not auto-end, and on the end of a block comment - ((and (not autoend) - (matlab-cursor-in-comment) - (let ((bcend (save-excursion - (beginning-of-line) - (re-search-forward "%" (point-at-eol)) - (goto-char (match-beginning 0)) - (when (looking-at "%}") - (point))))) - (if bcend (goto-char bcend)))) + (let ((p (point)) + (returnme t) + keyword) + (save-excursion + (skip-chars-backward " \t\n") + (cond + ;; Auto end - Just go! + (autoend + (when (matlab--scan-block-backward-up nil) + (if noerror + (setq returnme nil) + (error "Unstarted END"))) + ) - (let ((bc (matlab-block-comment-bounds))) - (goto-char (car bc))) - ) - ;; Not auto-end, and not looking @ and end type keyword - ((and (not autoend) - (save-excursion (backward-word 1) - (or (not - (and (looking-at - (matlab-block-end-no-function-re)) - (matlab-valid-end-construct-p))) - (matlab-cursor-in-string-or-comment)))) - ;; Go backwards one simple expression - (matlab-move-simple-sexp-internal -1)) + ;; No auto-end .... + + ;; End of a block comment + ((matlab-ltype-block-comment-end) + (beginning-of-line) + (matlab-beginning-of-string-or-comment)) - ;; otherwise go backwards recursively across balanced expressions - ;; backup over our end - (t - (if (not autoend) (forward-word -1)) - (let ((done nil) (start (point)) (returnme t) (bound nil)) - (when (search-backward "\nfunction" nil t) - (if (progn (forward-char 9) (looking-at "\\b")) - (setq bound (- (point) 8))) - (goto-char start)) - (while (and (not done) - (or (not matlab-scan-on-screen-only) - (pos-visible-in-window-p))) - (if (re-search-backward (matlab-block-scan-re) bound t) - (progn - (goto-char (match-beginning 2)) - (if (looking-at (matlab-block-end-no-function-re)) - (if (or (matlab-cursor-in-string-or-comment) - (not (matlab-valid-end-construct-p))) - nil - ;; we must skip the expression and keep searching - (forward-word 1) - (unless (matlab-backward-sexp nil noerror) - (setq done t - returnme nil))) - ;; Make sure we are at a valid block construct and not - ;; comments or other weird spot. - (if (and (not (matlab-cursor-in-string-or-comment)) - (matlab-cursor-on-valid-block-start)) - (setq done t)))) - (goto-char start) - (if noerror - (setq done t - returnme nil) - (error "Unstarted END construct")))) - returnme))))) + ((or (not (setq keyword (matlab-on-keyword-p))) + (memq keyword '(decl ctrl mcos arg vardecl keyword))) + ;; Just walk over block starts and other random stuff. + (matlab-move-simple-sexp-internal -1)) + + ((memq keyword '(mid case)) + ;; If we're on a middle, then assume we're in the middle + ;; of something and keep going. + (when (matlab--scan-block-backward-up nil) + (if noerror + (setq returnme nil) + (error "Unstarted END"))) + ) + + (t + (when (matlab--scan-block-backward nil) + (if noerror + (setq returnme nil) + (error "Unstarted END"))) + ) + ) + + (when returnme + (setq p (point)))) + (goto-char p) + returnme)) (defun matlab-forward-sexp (&optional includeelse autostart parentblock) "Go forward one balanced set of MATLAB expressions. @@ -1741,68 +1725,43 @@ forward until we exit that block. PARENTBLOCK is used when recursing to validate block starts as being in a valid context." (interactive "P") - (let (p) ;; go to here if no error. - (save-excursion ;; don't go anywhere if there is an error - (matlab-navigation-syntax - ;; skip over preceding whitespace - (skip-chars-forward " \t\n;") - (cond - ;; no autostart, and looking at a block comment. - ((and (not autostart) - (matlab-ltype-block-comment-start)) - (goto-char (match-end 0)) - (let ((bc (matlab-block-comment-bounds))) - (when bc (goto-char (cdr bc)))) - ) - ;; No autostart, and looking at a block keyword. - ((and (not autostart) - (or (not (looking-at (matlab-block-start-scan-re))) - ;; ^^ (concat "\\(" (matlab-block-beg-pre) "\\|" (matlab-block-mid-re) "\\)\\>") - (matlab-cursor-in-string-or-comment))) - ;; Go forwards one simple expression - (matlab-move-simple-sexp-internal 1)) + (let (p keyword) ;; go to here if no error. + (save-excursion ;; Don't move if there is an error + ;; skip over preceding whitespace + (skip-chars-forward " \t\n;") + (cond + ;; Auto start - just go! + (autostart + (when (matlab--scan-block-forward-up nil) + (error "Unterminated Block") + )) - ;; Yes autostart, but already looking @ the END! - ((and autostart (looking-at (matlab-block-end-re))) - (goto-char (match-end 0))) + ;; No Autostart .... - ;; Default behavior. - (t - ;; Not autostart, skip next word, and also track what it is - ;; for nesting purposes. - (unless autostart - (setq parentblock (buffer-substring-no-properties - (point) (progn (forward-word 1) (point))))) - (let ((done nil) (s nil) - (expr-scan (if includeelse - (matlab-block-re) - (matlab-block-scan-re))) - (expr-look (matlab-block-beg-pre))) - (while (and (not done) - (setq s (re-search-forward expr-scan nil t)) - (or (not matlab-scan-on-screen-only) - (pos-visible-in-window-p))) - (goto-char (match-beginning 2)) - (if (looking-at expr-look) - (if (or (matlab-cursor-in-string-or-comment) - ;; We'd like to do this: - ;;(not (matlab-cursor-on-valid-block-start)) - ;; but it travels backwards. Instead, we need to track the - ;; last block we are diving down from and just use that - ;; instead of looking it up along the way. - (not (matlab-cursor-on-valid-block-start parentblock)) - ) - (forward-word 1) - ;; we must skip the expression and keep searching - ;; NEVER EVER call with value of INCLUDEELSE - (matlab-forward-sexp nil nil (match-string-no-properties 1))) - (forward-word 1) - (if (and (not (matlab-cursor-in-string-or-comment)) - (matlab-valid-end-construct-p)) - (setq done t)))) - (if (not s) - (error "Unterminated block"))))) - (setq p (point)))) ;; really go here + ;; Looking at a block comment. + ((and (not autostart) + (looking-at "%")) + (goto-char (match-end 0)) + (matlab-end-of-string-or-comment)) + + ((or (not (setq keyword (matlab-on-keyword-p))) + (memq keyword '(end vardecl keyword))) + ;; Just walk over ends and other random stuff. + (matlab-move-simple-sexp-internal 1)) + + ((memq keyword '(mid case)) + ;; If we're on a middle, then assume we're in the middle + ;; of something and keep going. + (when (matlab--scan-block-forward-up nil) + (error "Unterminated Block")) + ) + + (t + (when (matlab--scan-block-forward nil nil) + (error "Unterminated Block")) + ) + ) + (setq p (point))) (goto-char p))) (defvar matlab-valid-block-start-slow-and-careful t @@ -2241,53 +2200,50 @@ by close, the first character is the end of an array." "Return a number representing the number of unterminated block constructs. This is any block, such as if or for, that doesn't have an END on this line. Optional EOL indicates a virtual end of line." - (let ((v 0) - (lvl (matlab-compute-line-context 1))) - (if (or (matlab-line-comment-p lvl) - (matlab-line-empty-p lvl)) - ;; If this line is comments or empty, no code to scan - 0 - (save-excursion - (beginning-of-line) - (save-restriction - (narrow-to-region (point) (or eol (save-excursion (matlab-line-end-of-code lvl) - (point)))) - (matlab-navigation-syntax - (while (re-search-forward (concat "\\<" (matlab-block-beg-re) "\\>") - nil t) - (if (or (matlab-cursor-in-string-or-comment) - (not (save-excursion (forward-word -1) - (matlab-cursor-on-valid-block-start)))) - ;; Do nothing if in comment, or if the thing we skipped over was - ;; an invalid block construct (based on local context) - nil - ;; Increment counter, move to end. - (setq v (1+ v)) - (let ((p (point))) - (forward-word -1) - (condition-case nil - (progn - (matlab-forward-sexp) - (setq v (1- v))) - (error (goto-char p)))))) - v)))))) - -(defun matlab-lattr-middle-block-cont () - "Return the number of middle block continuations. -This should be 1 or nil, and only true if the line starts with one of these -special items." (save-excursion - (back-to-indentation) - (if (looking-at (concat (matlab-block-mid-re) "\\>")) - (if (and (re-search-forward (matlab-block-end-pre) - (matlab-point-at-eol) - t) - (matlab-valid-end-construct-p)) - ;; If there is an END, we still need to return non-nil, - ;; but the number value is a net of 0. - 0 - 1) - nil))) + (let* ((v 0) + (lvl (matlab-compute-line-context 1)) + (bound (or eol (save-excursion (matlab-line-end-of-code lvl)))) + (keyword nil)) + + (if (or (matlab-line-comment-p lvl) + (matlab-line-empty-p lvl)) + ;; If this line is comments or empty, no code to scan + 0 + (back-to-indentation) + (while (setq keyword + (matlab-re-search-keyword-forward (matlab-keyword-regex 'indent) bound t)) + (if (or (and (eq (car keyword) 'mcos) + (not (matlab--valid-mcos-keyword-point nil))) + (and (eq (car keyword) 'args) + (not (matlab--valid-arguments-keyword-point nil)))) + ;; Not valid, skip it. + nil + ;; Increment counter, move to end. + (setq v (1+ v)) + (let ((p (point))) + (forward-word -1) + (if (matlab--scan-block-forward bound) ;;(matlab-forward-sexp) + (goto-char p) + (setq v (1- v)))))) + v)))) + +;;(defun matlab-lattr-middle-block-cont () +;; "Return the number of middle block continuations. +;;This should be 1 or nil, and only true if the line starts with one of these +;;special items." +;; (save-excursion +;; (back-to-indentation) +;; (if (looking-at (concat (matlab-block-mid-re) "\\>")) +;; (if (and (re-search-forward (matlab-block-end-pre) +;; (matlab-point-at-eol) +;; t) +;; (matlab-valid-end-construct-p)) +;; ;; If there is an END, we still need to return non-nil, +;; ;; but the number value is a net of 0. +;; 0 +;; 1) +;; nil))) (defun matlab-lattr-endless-block-cont () "Return the number of middle block continuations. @@ -2298,66 +2254,52 @@ special items." (if (looking-at (concat (matlab-endless-blocks-re) "\\>")) 1 nil))) - + (defun matlab-lattr-block-close (&optional start) "Return the number of closing block constructs on this line. Argument START is where to start searching from." (save-excursion (when start (goto-char start)) (let ((v 0) - (lvl1 (matlab-compute-line-context 1))) + (lvl1 (matlab-compute-line-context 1)) + (bound (save-excursion (matlab-scan-beginning-of-command)))) + (if (matlab-line-comment-p lvl1) ;; If this is even vagely a comment line, then there is no ;; need to do any scanning. 0 ;; Else, lets scan. - (matlab-with-current-command - ;; lets only scan from the beginning of the comment - (goto-char start) - (matlab-line-end-of-code lvl1) + ;; lets only scan from the beginning of the comment + (goto-char start) + (matlab-line-end-of-code lvl1) - ;; Count every END from our starting point till the beginning of the - ;; command. That count indicates unindent from the beginning of the - ;; command which anchors the starting indent. - (while (re-search-backward (concat "\\<" (matlab-block-end-re) "\\>") - nil t) - (let ((startmove (match-end 0)) - (nomove (point))) - (cond - ((matlab-beginning-of-string-or-comment) - ;; Above returns non-nil if it was in a string or comment. - ;; In that case, we need to keep going. - nil) - ((not (matlab-valid-end-construct-p)) - ;; Not a valid end, just move past it. - (goto-char nomove)) - ((matlab-line-declaration-p lvl1) - ;; In endless fuction buffers, a function marks the bounds - ;; of other functions, but in this case, it is meaningless, - ;; so do nothing with it. - (goto-char nomove)) - (t - ;; Lets count these end constructs. - (setq v (1+ v)) - (if (matlab-backward-sexp t t) - (setq v (1- v)) - (goto-char nomove))) - ))) - ;; If we can't scoot back, do a cheat-test to see if there - ;; is a matching else or elseif. - (goto-char (point-min)) - (back-to-indentation) - (if (looking-at (matlab-block-mid-re)) + ;; Count every END from our starting point till the beginning of the + ;; command. That count indicates unindent from the beginning of the + ;; command which anchors the starting indent. + (while (matlab-re-search-keyword-backward (matlab-keyword-regex 'end) bound t) + (let ((startmove (match-end 0)) + (nomove (point))) + ;; Lets count these end constructs. + (setq v (1+ v)) + (if (matlab--scan-block-backward bound) ;;(matlab-backward-sexp t t) + (goto-char nomove) (setq v (1- v))) - ;; Return nil, or a number - (if (<= v 0) nil v)))))) + )) + ;; If we can't scoot back, do a cheat-test to see if there + ;; is a matching else or elseif. + (goto-char bound) + (back-to-indentation) + (if (looking-at (matlab-block-mid-re)) + (setq v (1- v))) + ;; Return nil, or a number + (if (<= v 0) nil v))))) -(defun matlab-lattr-local-end () - "Return t if this line begins with an end construct." - (save-excursion - (back-to-indentation) - (and (looking-at (concat "\\<" (matlab-block-end-re) "\\>")) - (matlab-valid-end-construct-p)))) +;;(defun matlab-lattr-local-end () +;; "Return t if this line begins with an end construct." +;; (save-excursion +;; (back-to-indentation) +;; (and (looking-at (concat "\\<" (matlab-block-end-re) "\\>")) +;; (matlab-valid-end-construct-p)))) (defun matlab-function-called-at-point () "Return a string representing the function called nearby point." diff --git a/tests/blocks.m b/tests/blocks.m index ba33bcd..b40db76 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -25,7 +25,7 @@ classdef blocks < handle %properties %events %methods - arguments %!!8 + arguments %#ok %!!8 misc %!!8 end % <<13 @@ -78,7 +78,7 @@ classdef blocks < handle %>>20 function events=arguments(arguments)%!!8 arguments, arguments(:,:) {mustBeNumeric}, end %!!12 - + enumeration ... %!!12 = arguments; @@ -99,9 +99,9 @@ classdef blocks < handle % Try using the methods of this object obj.properties();%!!12 obj.methods();%!!12 - obj.events();%!!12 - obj.arguments();%!!12 - obj.enumeration();%!!12 + obj. events();%!!12 + obj. arguments();%!!12 + obj. enumeration();%!!12 normal();%!!12 end %<<22 @@ -126,7 +126,7 @@ classdef blocks < handle s = enumeration;%!!12 end %<<23 - + %>>24 function s = simple(~,arguments)%!!8 % Simple function @@ -161,10 +161,10 @@ classdef blocks < handle end%!!16 end%!!12 end%!!8 - + %!!8 end %<<14 - + %!!4 end % <<1 diff --git a/tests/continuations.m b/tests/continuations.m index 1c31191..1d6db28 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -53,8 +53,17 @@ function continuations(a,b) %!!0 end ... %!!4 code1(); %!!4 - this after those continued ends + + ... % !!4 A continuation with a ctrl block after it + % TODO - Remove this line and get things to pass + for g=1:10 % !!4 b/c continuation + localfcn(a,g) % !!8 b/c not continued, and +4 from comment continuation + end % !!4 to match + % !!4 to undo continuation. + + ... % Continuation by itself just before an end. end %!!0 diff --git a/tests/indents.m b/tests/indents.m index ef5d33b..8e02fde 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -108,7 +108,7 @@ function B = ends_in_comments_and_strings() fcncall(arg1, '...', arg3); % !!4 1; % !!4 - % Multi-ends + % Multi- end s % >>8 if foo %#ok if bar %#ok @@ -167,13 +167,20 @@ function out = array_constant_decls() % 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 ; ... % Another row. + ]; % !!4 out = { A %!!4 Blong %!!12 Csep %!!12 multinest%!!12 + Closures%!!12 }; %!!10 end @@ -188,7 +195,7 @@ function C = block_starts_in_comments_and_strings(varargin) % !!8 else % !!4 - % !!8 + % !!8 end % if true diff --git a/tests/metest.el b/tests/metest.el index d79b099..600d5c6 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -211,7 +211,7 @@ (kill-buffer buf) (list cnt "tests"))) -(defvar metest-sexp-traversal-test (cons "sexp traversal" met-sexptest-files)) +(defvar metest-sexp-traversal-test (cons "sexp block traversal" met-sexptest-files)) (defun metest-sexp-traversal-test (F) "Run a test to make sure high level block navigation works." (let ((buf (metest-find-file F)) @@ -225,19 +225,20 @@ (begin nil)) (skip-chars-forward " \n\t;%") (setq begin (point)) - (metest-condition-case-error-msg (matlab-forward-sexp)) - (skip-chars-forward " \n\t;%") - (if (not (looking-at "<<\\([0-9]+\\)")) - (metest-error "Failed to find matching test end token for %d" - num) - (setq num2 (string-to-number (match-string 1))) - (when (/= num num2) - (metest-error "Failed to match correct test token. Start is %d, end is %d" - num num2))) - (matlab-backward-sexp) + (metest-condition-case-error-msg (matlab--scan-block-forward)) + (save-excursion + (skip-chars-forward " \n\t;%") + (if (not (looking-at "<<\\([0-9]+\\)")) + (metest-error "Failed to find matching test end token for %d" + num) + (setq num2 (string-to-number (match-string 1))) + (when (/= num num2) + (metest-error "Failed to match correct test token. Start is %d, end is %d" + num num2)))) + (metest-condition-case-error-msg (matlab--scan-block-backward)) (when (/= (point) begin) (metest-error "Failed to reverse navigate sexp for %d" - num)) + num)) ) (end-of-line) (setq cnt (1+ cnt)))) diff --git a/tests/mfuncnoend.m b/tests/mfuncnoend.m index 027a0ab..0dfff4d 100644 --- a/tests/mfuncnoend.m +++ b/tests/mfuncnoend.m @@ -2,14 +2,14 @@ function mfuncnoend % A function file that does not have ends at the end of functions. % % %%% function nil - + fcn_call(1) - + function fcn_call(idx) - + if idx > 0 fcn_call(ix-1) end - - \ No newline at end of file + + diff --git a/tests/mfuncnoendblock.m b/tests/mfuncnoendblock.m index 8e63953..a3ca026 100644 --- a/tests/mfuncnoendblock.m +++ b/tests/mfuncnoendblock.m @@ -15,6 +15,7 @@ function fcn_call(idx) %!!0 if idx > 0 %!!0 fcn_call(ix-1) %!!4 + goo(3); end %!!0 function c=goo(p3) %!!0 From b1d720eff5c97f84667a770062c47ebe245146ac Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 00:41:42 -0400 Subject: [PATCH 079/168] matlab-scan.el: (matlab-scan-block-start-context): New matlab.el: (matlab-lattr-block-cont): Rename to (matlab-lattr-block-open): New name, return 0 not nil. (matlab--calc-indent, matlab-show-line-info): Call matlab-next-line-indent w/ new inputs. (matlab-calculate-indentation-1): Replace old END indent logic with call to `matlab-scan-block-start-context' and then stabilize dedent to match block start. (matlab-next-line-indentation): Better inputs based on what's used. Delete dead code and commented out code. tests/continuations.m: Enabled test that now 'works'. --- matlab-scan.el | 21 +++++++ matlab.el | 125 ++++++++++++++++-------------------------- tests/continuations.m | 13 ++--- 3 files changed, 74 insertions(+), 85 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index fbd9670..330521f 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1051,6 +1051,27 @@ Instead, travel to end as if on keyword." ))) (when ans (goto-char ans) (matlab--mk-keyword-node)))) +;;; Quick Queries +;; +(defun matlab-scan-block-start-context () + "Return a context for the block start matching block point is in. +assumes pt is NOT on an end. List contains: + 0 - type of keyword the end matched. + 1 - column the keyword is on. + 2 - lvl1 context of line the keyword is on + 3 - lvl1 context of line at beginning of cmnd found keyword is in. + +Items 2 and 3 are likely the same but could be different." + (save-excursion + (matlab--scan-block-backward-up) + (let* ((keyword (matlab-on-keyword-p)) + (column (current-column)) + (lvl1-match (matlab-compute-line-context 1)) + (lvl1-bgn (save-excursion (matlab-scan-beginning-of-command lvl1-match) + (matlab-compute-line-context 1)))) + (list keyword column lvl1-match lvl1-bgn)))) + + ;;; Caching ;; diff --git a/matlab.el b/matlab.el index 509117a..19aced2 100644 --- a/matlab.el +++ b/matlab.el @@ -2196,7 +2196,7 @@ by close, the first character is the end of an array." (back-to-indentation) (and (looking-at "[]}]") (matlab-lattr-array-cont)))) -(defun matlab-lattr-block-cont (&optional eol) +(defun matlab-lattr-block-open (&optional eol) "Return a number representing the number of unterminated block constructs. This is any block, such as if or for, that doesn't have an END on this line. Optional EOL indicates a virtual end of line." @@ -2292,7 +2292,7 @@ Argument START is where to start searching from." (if (looking-at (matlab-block-mid-re)) (setq v (1- v))) ;; Return nil, or a number - (if (<= v 0) nil v))))) + (if (<= v 0) 0 v))))) ;;(defun matlab-lattr-local-end () ;; "Return t if this line begins with an end construct." @@ -2422,7 +2422,7 @@ This function exists so the test harness can override it." (let ((prevcmd (or (matlab-previous-code-line lvl2) (matlab-previous-line lvl2)))) (matlab-with-context-line prevcmd - (matlab-next-line-indentation lvl2 prevcmd))))) + (matlab-next-line-indentation prevcmd))))) ) ;; Compute this line's indentation based on recommendation of previous @@ -2515,46 +2515,48 @@ LVL2 is a level 2 scan context with info from previous lines." ;; END keyword ((matlab-line-end-p lvl1) ;;(matlab-lattr-local-end) - (let ((end-of-function - (let ((matlab-functions-have-end t)) - (save-excursion - (beginning-of-line) - (matlab-backward-sexp t t) - (matlab-ltype-function-definition))))) - (if end-of-function + (let* ((CTXT (matlab-with-context-line lvl1 + (matlab-scan-block-start-context)))) + + (if (eq (car CTXT) 'decl) ;; declarations (ie - function) is treated special. (if (or matlab-functions-have-end (if (matlab--maybe-yes-or-no-p matlab-functions-have-end-should-be-true t) (matlab-functions-have-end-minor-mode 1) (error "Unmatched end"))) (if (matlab-indent-function-body-p) - (setq ci (- ci matlab-indent-level)))) + ;;(setq ci (- ci matlab-indent-level)) + ;; match indentation of the function regardless of any other + ;; state that might have gotten messed up. + (setq ci (matlab-line-indentation (nth 3 CTXT))) + )) ;; Next, see if this line starts with an end, and whether the ;; end is matched, and whether the line is blank up to the match. ;; If so, return the indentation of the match. - (catch 'indent - (save-excursion - (when (progn (beginning-of-line) - (and (looking-at "[ \t]*end\\b") - (matlab-backward-sexp t t))) - (let ((match (point))) - (beginning-of-line) - (looking-at "[ \t]*") - (when (= match (match-end 0)) - (let ((match-col-end - (save-excursion - (goto-char match) - (current-column))) - (match-col-beginning - (save-excursion - (goto-char (match-beginning 0)) - (current-column))) - ) - (setq ci (- match-col-end match-col-beginning))) - (throw 'indent nil))))) - ;; End of special case for end and match after "^[ \t]*". - (setq ci (+ ci - (* (1- (matlab-lattr-block-cont (point))) - matlab-indent-level)))))) + (setq ci (matlab-line-indentation (nth 3 CTXT))))) + ;;(catch 'indent + ;; (save-excursion + ;; (when (progn (beginning-of-line) + ;; (and (looking-at "[ \t]*end\\b") + ;; (matlab-backward-sexp t t))) + ;; (let ((match (point))) + ;; (beginning-of-line) + ;; (looking-at "[ \t]*") + ;; (when (= match (match-end 0)) + ;; (let ((match-col-end + ;; (save-excursion + ;; (goto-char match) + ;; (current-column))) + ;; (match-col-beginning + ;; (save-excursion + ;; (goto-char (match-beginning 0)) + ;; (current-column))) + ;; ) + ;; (setq ci (- match-col-end match-col-beginning))) + ;; (throw 'indent nil))))) + ;; ;; End of special case for end and match after "^[ \t]*". + ;; (setq ci (+ ci + ;; (* (1- (matlab-lattr-block-open (point))) + ;; matlab-indent-level)))))) (list 'blockend ci)) ;; ELSE/CATCH keywords ((matlab-line-block-middle-p lvl1) @@ -2725,49 +2727,21 @@ LVL2 is a level 2 scan context with info from previous lines." cc))))))))) ))) -(defun matlab-next-line-indentation (lvl2 prevlvl1) +(defun matlab-next-line-indentation (lvl1) "Calculate the indentation for lines following this command line. -Assume that the following line does not contribute its own indentation -\(as it does in the case of nested functions in the following situations): - o function---positive indentation when not indenting function bodies. - o end---negative indentation except when the 'end' matches a function and - not indenting function bodies. -See `matlab-calculate-indentation'." +See `matlab-calculate-indentation' for how the output of this fcn is used." (let ((startpnt (point-at-eol)) - (lvl1 nil) ) (save-excursion (matlab-scan-beginning-of-command lvl1) - ;;(let ((plvl2 (matlab-previous-line-lvl2 lvl2))) - ;; (matlab-previous-command-begin plvl2)) - ;;(matlab-beginning-of-command) (back-to-indentation) (setq lvl1 (matlab-compute-line-context 1)) - (let ((cc (or (matlab-lattr-block-close startpnt) 0)) - (bc (matlab-lattr-block-cont startpnt)) - (end (matlab-line-end-p lvl1)) ;(matlab-lattr-local-end)) - (mc (and (matlab-line-block-middle-p lvl1) 1)) ;(matlab-lattr-middle-block-cont)) - (ec (and (matlab-line-block-case-p lvl1) 1)) ;(matlab-lattr-endless-block-cont)) - ;; TODO: The old impl of HC here - not sure what it did. point was always on the fcn decl - ;; so this would always be wrong. Leaving out to see what happens. - (hc nil) - ;;(and (matlab-last-guess-decl-p) - ;;(matlab-indent-function-body-p) - ;; ;;(matlab-ltype-help-comm) - ;;(matlab-scan-comment-help-p lvl2) - ;;)) - - ;; TODO: Ol impl of RC here - but not sure what this is doing either. It doesn't - ;; seem to ever be t in my tests. - (rc nil) - ;; (and (/= 0 matlab-comment-anti-indent) - ;; (matlab-line-regular-comment-p lvl1) ;(matlab-ltype-comm-noblock) - ;; ;;(not (matlab-ltype-help-comm)) - ;; (not (matlab-ltype-continued-comm)) - ;; (message "RC found a thing.") - ;; )) - + (let ((cc (matlab-lattr-block-close startpnt)) + (bc (matlab-lattr-block-open startpnt)) + (end (matlab-line-end-p lvl1)) + (mc (and (matlab-line-block-middle-p lvl1) 1)) + (ec (and (matlab-line-block-case-p lvl1) 1)) (ci (current-indentation))) ;; When the current point is on a line with a function, the value of bc will ;; reflect the function in a block count iff if matlab-functions-have-end is @@ -2780,8 +2754,8 @@ See `matlab-calculate-indentation'." (if matlab-functions-have-end (if (and (not (matlab-indent-function-body-p)) - (or (matlab-line-declaration-p lvl1) ;(matlab-ltype-function-definition) - (and (matlab-line-end-p lvl1) ;(matlab-lattr-local-end) + (or (matlab-line-declaration-p lvl1) + (and (matlab-line-end-p lvl1) (save-excursion (matlab-backward-sexp t) (looking-at "function\\b"))))) @@ -2791,7 +2765,7 @@ See `matlab-calculate-indentation'." (setq bc -1)))) ;; Else, funtions don't have ends in this file. (if (and (matlab-indent-function-body-p) - (matlab-line-declaration-p lvl1)) ; (matlab-ltype-function-definition)) + (matlab-line-declaration-p lvl1)) (setq bc (1+ bc)))) ;; Remove 1 from the close count if there is an END on the beginning ;; of this line, since in that case, the unindent has already happened. @@ -2804,8 +2778,6 @@ See `matlab-calculate-indentation'." (* (if (listp matlab-case-level) (cdr matlab-case-level) matlab-case-level) (or ec 0)) - (if hc matlab-indent-level 0) - (if rc (- 0 matlab-comment-anti-indent) 0) ))))) ;;; The return key ============================================================ @@ -4125,8 +4097,7 @@ desired. Optional argument FAST is not used." (lvl2 (matlab-compute-line-context 2)) (indent nil) (fullindent (matlab--calc-indent lvl2 'indent)) - (nexti (matlab-next-line-indentation (matlab-previous-line-lvl2 lvl2) - (matlab-get-lvl1-from-lvl2 lvl2)))) + (nexti (matlab-next-line-indentation (matlab-previous-line lvl2)))) (setq msg (concat msg " Line type: " (symbol-name (car indent)) " This Line: " (int-to-string (nth 1 indent)) diff --git a/tests/continuations.m b/tests/continuations.m index 1d6db28..856b594 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -48,23 +48,20 @@ function continuations(a,b) %!!0 if true, if true, if true %!!4 localfcn(a,b) ... %!!16 - end; ... %!!12 - end; ... %!!8 + end; ... %!!4 + end; ... %!!4 end ... %!!4 code1(); %!!4 - this after those continued ends - + ... % !!4 A continuation with a ctrl block after it - % TODO - Remove this line and get things to pass - for g=1:10 % !!4 b/c continuation + for g=1:10 % !!8 b/c continuation localfcn(a,g) % !!8 b/c not continued, and +4 from comment continuation - end % !!4 to match + end % !!4 to match % !!4 to undo continuation. - - ... % Continuation by itself just before an end. end %!!0 From 099433517c83482916f72ba2a9301c8c2d09627b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 01:15:00 -0400 Subject: [PATCH 080/168] matlab.el: (matlab-lattr-block-close): Stop decrementing for else type keywords. (matlab-calculate-indentation-1): Allow block comments to be help comments. tests/continuations.m: Add suite of previously failing tests. tests/indents.m: Block comments can now be help. --- matlab.el | 18 +++++++++--------- tests/continuations.m | 41 ++++++++++++++++++++++++++++++++++++++--- tests/indents.m | 14 ++++++++------ 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/matlab.el b/matlab.el index 19aced2..3533dd8 100644 --- a/matlab.el +++ b/matlab.el @@ -2287,10 +2287,10 @@ Argument START is where to start searching from." )) ;; If we can't scoot back, do a cheat-test to see if there ;; is a matching else or elseif. - (goto-char bound) - (back-to-indentation) - (if (looking-at (matlab-block-mid-re)) - (setq v (1- v))) + ;;(goto-char bound) + ;;(back-to-indentation) + ;;(if (looking-at (matlab-block-mid-re)) + ;; (setq v (1- v))) ;; Return nil, or a number (if (<= v 0) 0 v))))) @@ -2472,11 +2472,6 @@ LVL2 is a level 2 scan context with info from previous lines." ((matlab-line-comment-p lvl1) (let ((comment-style (matlab-line-comment-style lvl1))) (cond - ;; BLOCK START is like regular comment - ((eq comment-style 'block-start) - ;; indent like code, but some users like anti-indent - (list 'comment (+ ci matlab-comment-anti-indent)) - ) ;; BLOCK END undoes body indent ((or (eq comment-style 'block-end) (eq comment-style 'block-body-prefix)) ; body prefix has same lineup rule @@ -2488,6 +2483,11 @@ LVL2 is a level 2 scan context with info from previous lines." ((and (matlab-last-guess-decl-p) (setq tmp (matlab-scan-comment-help-p lvl2))) (list 'comment-help tmp)) + ;; BLOCK START is like regular comment + ((eq comment-style 'block-start) + ;; indent like code, but some users like anti-indent + (list 'comment (+ ci matlab-comment-anti-indent)) + ) ;; COMMENT REGION comments ((matlab-line-comment-ignore-p lvl1) (list 'comment-ignore 0)) diff --git a/tests/continuations.m b/tests/continuations.m index 856b594..df80d20 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -58,19 +58,54 @@ function continuations(a,b) %!!0 ... % !!4 A continuation with a ctrl block after it for g=1:10 % !!8 b/c continuation localfcn(a,g) % !!8 b/c not continued, and +4 from comment continuation - end % !!4 to match + end % !!4 to match % !!4 to undo continuation. ... % Continuation by itself just before an end. end %!!0 +function a=odd_if_location(n) %!!0 + + i=1; while i<10, if xfcn(i,n)==0, i=i+1; %!!4 + else, i=20; end; end %!!21 + + %% FIXME - this should go back to col 4 + if i<20 %!!17 + a=1; %!!21 + else %!!17 + a=0; %!!21 + end %!!17 +end %!!0 + +function a=foo +%{ + for !!2 + for !!2 +%} + if true %!!4 + if true %!!8 + a = 1; %!!12 + end end %!!8 +end %!!0 + function val = localfcn(c,d) %!!0 % !!0 Help Comment - + try fclose( fid ); catch, end %!!4 val = c+d; % !!4 -end %!!0 \ No newline at end of file +end %!!0 + +function [z,o,n]=odd_end_location_from_dspfwiz_load(opts) %!!0 + if opts.zeros, z = 'On'; %!!4 + else, z = 'Off'; end %!!4 + + if opts.ones, o = 'On'; %!!4 + else, o = 'Off'; end %!!4 + + if opts.neg_ones, n = 'On'; %!!4 + else, n = 'Off'; end %!!4 +end %!!0 diff --git a/tests/indents.m b/tests/indents.m index 8e02fde..04cd4d2 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -236,10 +236,12 @@ function B = continuations_and_block_comments % !!0 % !!0 - %{ - !!6 - !!6 - %} +%{ + !!2 + !!2 +%} + + arg1=1; %{ % !!4 @@ -247,8 +249,8 @@ function B = continuations_and_block_comments % !!4 %} -% Block comment indicators MUST be on a line by themselves. -%{ Not a block comment } + % Block comment indicators MUST be on a line by themselves. + %{ Not a block comment } foo(1); % !!4 - don't indent this special From b4f10db9b98b6fe32b2f7276fbee0cd0a886f73d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 01:27:16 -0400 Subject: [PATCH 081/168] matlab.el: (matlab-next-line-indentation): Add special code for multi-end lines to find originating line and use that indentation. tests/continuations.m: Set the indentation for section previous broken. --- matlab.el | 86 +++++++++++++++++++++++++------------------ tests/continuations.m | 16 ++++---- 2 files changed, 58 insertions(+), 44 deletions(-) diff --git a/matlab.el b/matlab.el index 3533dd8..7d650d1 100644 --- a/matlab.el +++ b/matlab.el @@ -2743,42 +2743,56 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (mc (and (matlab-line-block-middle-p lvl1) 1)) (ec (and (matlab-line-block-case-p lvl1) 1)) (ci (current-indentation))) - ;; When the current point is on a line with a function, the value of bc will - ;; reflect the function in a block count iff if matlab-functions-have-end is - ;; true. However, if matlab-indent-function-body-p is false, there should be - ;; no actual indentation, so bc needs to be decremented by 1. Similarly, if - ;; on a line with an end that closes a function, bc needs to be decremented - ;; by 1 if matlab-functions-have-end is true and matlab-indent-function-body-p - ;; is false. However, just to be safe, indentation is not allowed to go - ;; negative. Thus: - (if matlab-functions-have-end - (if (and - (not (matlab-indent-function-body-p)) - (or (matlab-line-declaration-p lvl1) - (and (matlab-line-end-p lvl1) - (save-excursion - (matlab-backward-sexp t) - (looking-at "function\\b"))))) - (if (> bc 0) - (setq bc (1- bc)) - (if (>= ci matlab-indent-level) - (setq bc -1)))) - ;; Else, funtions don't have ends in this file. - (if (and (matlab-indent-function-body-p) - (matlab-line-declaration-p lvl1)) - (setq bc (1+ bc)))) - ;; Remove 1 from the close count if there is an END on the beginning - ;; of this line, since in that case, the unindent has already happened. - (when end (setq cc (1- cc))) - ;; Calculate the suggested indentation. - (+ ci - (* matlab-indent-level bc) - (* matlab-indent-level (or mc 0)) - (* matlab-indent-level (- cc)) - (* (if (listp matlab-case-level) - (cdr matlab-case-level) matlab-case-level) - (or ec 0)) - ))))) + + ;; When CC is positive, and END is false, or CC > 1, then the NEXT + ;; line should have an indent that matches the context at the beginning + ;; of the block of the last end. + (if (or (> cc 1) (and (= cc 1) end)) + (let* ((CTXT (matlab-with-context-line lvl1 + (matlab-line-end-of-code lvl1) + (matlab-re-search-keyword-backward + (matlab-keyword-regex 'end) (point-at-bol) t) + (matlab-scan-block-start-context)))) + (matlab-line-indentation (nth 3 CTXT))) + + ;; Old technique. + + ;; When the current point is on a line with a function, the value of bc will + ;; reflect the function in a block count iff if matlab-functions-have-end is + ;; true. However, if matlab-indent-function-body-p is false, there should be + ;; no actual indentation, so bc needs to be decremented by 1. Similarly, if + ;; on a line with an end that closes a function, bc needs to be decremented + ;; by 1 if matlab-functions-have-end is true and matlab-indent-function-body-p + ;; is false. However, just to be safe, indentation is not allowed to go + ;; negative. Thus: + (if matlab-functions-have-end + (if (and + (not (matlab-indent-function-body-p)) + (or (matlab-line-declaration-p lvl1) + (and (matlab-line-end-p lvl1) + (save-excursion + (matlab-backward-sexp t) + (looking-at "function\\b"))))) + (if (> bc 0) + (setq bc (1- bc)) + (if (>= ci matlab-indent-level) + (setq bc -1)))) + ;; Else, funtions don't have ends in this file. + (if (and (matlab-indent-function-body-p) + (matlab-line-declaration-p lvl1)) + (setq bc (1+ bc)))) + ;; Remove 1 from the close count if there is an END on the beginning + ;; of this line, since in that case, the unindent has already happened. + (when end (setq cc (1- cc))) + ;; Calculate the suggested indentation. + (+ ci + (* matlab-indent-level bc) + (* matlab-indent-level (or mc 0)) + (* matlab-indent-level (- cc)) + (* (if (listp matlab-case-level) + (cdr matlab-case-level) matlab-case-level) + (or ec 0)) + )))))) ;;; The return key ============================================================ diff --git a/tests/continuations.m b/tests/continuations.m index df80d20..cba58ad 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -69,13 +69,13 @@ function a=odd_if_location(n) %!!0 i=1; while i<10, if xfcn(i,n)==0, i=i+1; %!!4 else, i=20; end; end %!!21 - - %% FIXME - this should go back to col 4 - if i<20 %!!17 - a=1; %!!21 - else %!!17 - a=0; %!!21 - end %!!17 + + if i<20 %!!4 + a=1; %!!8 + else %!!4 + a=0; %!!8 + end %!!4 + end %!!0 function a=foo @@ -102,7 +102,7 @@ end %!!0 function [z,o,n]=odd_end_location_from_dspfwiz_load(opts) %!!0 if opts.zeros, z = 'On'; %!!4 else, z = 'Off'; end %!!4 - + if opts.ones, o = 'On'; %!!4 else, o = 'Off'; end %!!4 From a3dc83e1dcebd34505e96ab289d75fb1f25e1b7c Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 08:13:21 -0400 Subject: [PATCH 082/168] matlab-scan.el: (matlab--scan-block-forward): When fcns have no end, and a new fcn block start is found and popped, also go to beginning of found keyword so next forward-sexp sees it and jumps over next fcn. matlab.el: (matlab-backward-sexp, matlab-forward-sexp): Change chars skipped over @ beginning to better represent what regular forward/backwar-sexp does. --- matlab-scan.el | 13 ++++++------- matlab.el | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 330521f..48ea7bd 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -947,7 +947,8 @@ Use STATE to stop/start block scanning partway through." ;; b/c you can't have nested functions, but only if the thing we try to match ;; it to is another fcn. ;; This POP should result in empty state. - (pop blockstate)) + (pop blockstate) + (goto-char (match-beginning 1))) (t (push thiskeyword blockstate))) )) @@ -989,14 +990,12 @@ Use STATE to stop/start block scanning partway through." ;; On end, push this keyword (push thiskeyword blockstate)) ((eq (car thiskeyword) 'mcos) - (if (matlab--valid-mcos-keyword-point nil) - (pop blockstate) - ;; else, just skip it + (when (matlab--valid-mcos-keyword-point nil) + (pop blockstate) )) ((eq (car thiskeyword) 'args) - (if (matlab--valid-arguments-keyword-point nil) - (pop blockstate) - ;; else, just skip it, not a keyword + (when (matlab--valid-arguments-keyword-point nil) + (pop blockstate) )) (t (pop blockstate))) diff --git a/matlab.el b/matlab.el index 7d650d1..88f4aaa 100644 --- a/matlab.el +++ b/matlab.el @@ -1673,7 +1673,7 @@ This assumes that expressions do not cross \"function\" at the left margin." (returnme t) keyword) (save-excursion - (skip-chars-backward " \t\n") + (skip-syntax-backward " .><") (cond ;; Auto end - Just go! (autoend @@ -1728,7 +1728,7 @@ a valid context." (let (p keyword) ;; go to here if no error. (save-excursion ;; Don't move if there is an error ;; skip over preceding whitespace - (skip-chars-forward " \t\n;") + (skip-syntax-forward " .><") (cond ;; Auto start - just go! (autostart From 74a4c1f724b9fcf3119a4d4c6dd253649537848a Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 08:32:47 -0400 Subject: [PATCH 083/168] tests/continuations.m: Clean up mlint warnings. --- tests/continuations.m | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/continuations.m b/tests/continuations.m index cba58ad..fb37e5f 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -46,17 +46,17 @@ function continuations(a,b) %!!0 a,b); %!!8 - if true, if true, if true %!!4 - localfcn(a,b) ... %!!16 + if true, if true, if true %#ok %!!4 + localfcn(a,b); ... %!!16 end; ... %!!4 end; ... %!!4 end ... %!!4 - code1(); %!!4 - this after those continued ends + odd_if_location(); %!!4 - this after those continued ends ... % !!4 A continuation with a ctrl block after it - for g=1:10 % !!8 b/c continuation + for g=1:10 %#ok % !!8 b/c continuation localfcn(a,g) % !!8 b/c not continued, and +4 from comment continuation end % !!4 to match @@ -76,20 +76,9 @@ function a=odd_if_location(n) %!!0 a=0; %!!8 end %!!4 + foo(); end %!!0 -function a=foo -%{ - for !!2 - for !!2 -%} - if true %!!4 - if true %!!8 - a = 1; %!!12 - end end %!!8 -end %!!0 - - function val = localfcn(c,d) %!!0 % !!0 Help Comment @@ -97,6 +86,8 @@ function val = localfcn(c,d) %!!0 val = c+d; % !!4 + odd_end_location_from_dspfwiz_load(1); + end %!!0 function [z,o,n]=odd_end_location_from_dspfwiz_load(opts) %!!0 @@ -109,3 +100,16 @@ function [z,o,n]=odd_end_location_from_dspfwiz_load(opts) %!!0 if opts.neg_ones, n = 'On'; %!!4 else, n = 'Off'; end %!!4 end %!!0 + +function a=foo +%{ + for !!2 + for !!2 +%} + if true %#ok %!!4 + if true %!!8 + a = 1; %!!12 + end end %#ok %!!8 +end %!!0 + + From c9dab6d6cd70c190edb12485b59fad08faa89b90 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 08:36:17 -0400 Subject: [PATCH 084/168] matlab-scan.el: (matlab-functions-have-end, matlab-indent-level): Forward declare to quiet byte compiler. --- matlab-scan.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matlab-scan.el b/matlab-scan.el index 48ea7bd..26e318b 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -808,6 +808,9 @@ This function walks down past continuations and open arrays." ;; ;; Focused scanning across block structures, like if / else / end. +(defvar matlab-functions-have-end) +(defvar matlab-indent-level) + (defsubst matlab--mk-keyword-node () "Like `matlab-on-keyword-p', but returns a node for block scanning. The elements of the return node are: From 12d9cfbfe33a7403c6d8f73ec7474a2b92af92da Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 12:02:28 -0400 Subject: [PATCH 085/168] matlab-scan.el: (matlab--valid-mcos-keyword-point, matlab--valid-arguments-keyword-point): Require these special keywords to be first on the line. tests/mclass.m: Add a bunch of badly named methods. --- matlab-scan.el | 58 ++++++++++++++++++++++++++++---------------------- tests/mclass.m | 27 +++++++++++++++++++++++ 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 26e318b..ac71d64 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -848,25 +848,26 @@ This means that the parent block is a classdef. Optional input PARENTBLOCK is a precomputed keyword node representing the current block context point is in. Assume basic keyword checks have already been done." - ;; If a parent was provided, use that. - (if (matlab--known-parent-block parentblock) - (string= (nth 1 parentblock) "classdef") + (and + ;; Must be first thing on line - before checking other stuff. + (save-excursion (skip-syntax-backward "w") (skip-syntax-backward " ") (bolp)) + + ;; If a parent was provided, use that. + (if (matlab--known-parent-block parentblock) + (string= (nth 1 parentblock) "classdef") - (and (eq matlab-functions-have-end 'class) ;; not a class, no mcos allowed. - (save-excursion (skip-syntax-backward "w") - (skip-syntax-backward " ") - (bolp)) ;; Must be first on line. - - ;; Otherwise, roll back a single command. in MUST be - ;; an END indent 4 or CLASSDEF - (save-excursion - (skip-syntax-backward "w") - (forward-comment -100000) - (back-to-indentation) ;; TODO -> this should be beginning-of-command - (let ((prev (matlab--mk-keyword-node))) - (or (string= (nth 1 prev) "classdef") - (and (string= (nth 1 prev) "end") - (= (current-indentation) matlab-indent-level)))))))) + ;; else more expensive check + (and (eq matlab-functions-have-end 'class) ;; not a class, no mcos allowed. + ;; Otherwise, roll back a single command. in MUST be + ;; an END indent 4 or CLASSDEF + (save-excursion + (skip-syntax-backward "w") + (forward-comment -100000) + (back-to-indentation) ;; TODO -> this should be beginning-of-command + (let ((prev (matlab--mk-keyword-node))) + (or (string= (nth 1 prev) "classdef") + (and (string= (nth 1 prev) "end") + (= (current-indentation) matlab-indent-level))))))))) (defun matlab--valid-arguments-keyword-point (&optional parentblock) "Return non-nil if at a location that is valid for ARGUMENTS keyword. @@ -875,14 +876,19 @@ the function. Optional input PARENTBLOCK is a precomputed keyword node representing the current block context point is in. Assume basic keyword checks have already been done." - (let ((parent - (or (matlab--known-parent-block parentblock) ;; technically this can lie, but it's fast. - (save-excursion (skip-syntax-backward "w") - (forward-comment -100000) - (matlab-scan-beginning-of-command) - (and (matlab--valid-keyword-point) - (matlab--mk-keyword-node)))))) - (string= (nth 1 parent) "function"))) + (save-excursion + (skip-syntax-backward "w") + (and + ;; Must be first thing on line - before checking other stuff. + (save-excursion (skip-syntax-backward "w") (skip-syntax-backward " ") (bolp)) + ;; More expensive checks + (let ((parent + (or (matlab--known-parent-block parentblock) ;; technically this can lie, but it's fast. + (save-excursion (forward-comment -100000) + (matlab-scan-beginning-of-command) + (and (matlab--valid-keyword-point) + (matlab--mk-keyword-node)))))) + (string= (nth 1 parent) "function"))))) (defun matlab--scan-derive-block-state (providedstate filter) "Return a block state for current point. diff --git a/tests/mclass.m b/tests/mclass.m index f078fc2..a0cf294 100644 --- a/tests/mclass.m +++ b/tests/mclass.m @@ -110,6 +110,33 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# end %!!4 + methods %!!4 + function properties(~) %!!8 + end %!!8 + + function methods(~) %!!8 + end %!!8 + + function events(~) %!!8 + end %!!8 + + function arguments(~) %!!8 + end %!!8 + + function enumeration(~) %!!8 + end %!!8 + + function usestuff(obj) %!!8 + % Try using the methods of this object + obj.properties(); %!!12 + obj.methods(); %!!12 + obj.events(); %!!12 + obj.arguments(); %!!12 + obj.enumeration(); %!!12 + end %!!8 + + end %!!4 + end % <<1 % End \ No newline at end of file From ef7894cea5975a6e8d3b295b9777064ab4193580 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 12:11:39 -0400 Subject: [PATCH 086/168] matlab-syntax.el: (matlab--scan-line-for-unterminated-string): When comment found, disable all quote chars in the comment. (matlab--scan-line-comment-disable-strings): Find all syntax " chars and disable as whitespace. tests/indents.m: Add tests with ' in comments that impact indent. --- matlab-syntax.el | 14 +++++++++++++- tests/indents.m | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index d1bea60..d848ec7 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -237,7 +237,9 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (start-char (match-beginning 0))) (forward-char -1) (if (looking-at "\\s<") - (forward-comment 1) + (progn + (matlab--scan-line-comment-disable-strings) + (forward-comment 1)) ;; Else, check for valid string (if (or (bolp) (string= start-str "\"") @@ -267,6 +269,16 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (error t)))) +(defun matlab--scan-line-comment-disable-strings () + "Disable bad string chars syntax from point to eol. +Called when comments found in `matlab--scan-line-for-unterminated-string'." + (save-excursion + (while (re-search-forward "\\s\"" nil t) + (save-excursion + (forward-char -1) + (matlab--put-char-category (point) 'matlab--transpose-syntax)) + ))) + (defun matlab--scan-line-bad-blockcomment () "Scan this line for invalid block comment starts." (when (and (re-search-forward "%{" (point-at-eol) t) (not (looking-at "\\s-*$"))) diff --git a/tests/indents.m b/tests/indents.m index 04cd4d2..9c4fc9c 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -170,17 +170,23 @@ function out = array_constant_decls() % This array has bad syntactic expression parsing due to the % apostrophy - Closures = [ ... + Closures = [ 755009 ; ... % 21-Feb-2067 Washington's Birthday (Mon) - 755010 ; ... % Another row. + 755010 ; % !!8 ]; + dep = [ + root(info.function, factory, workspace, []), ... % likewise this isn't a keyword + fcn3.finalize % the single quote used to break [] scanning + ]; + % !!4 out = { A %!!4 Blong %!!12 Csep %!!12 multinest%!!12 Closures%!!12 + dep %!!12 }; %!!10 end From 125206e4f0c19be074cb0feaa56ce299e48045ee Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 12:49:10 -0400 Subject: [PATCH 087/168] matlab.el: Delete random commented out code. Change fcns that ref renamed variables. (matlab-mode-version): Update! (matlab-cont-level): Rename to (matlab-continuation-indent-level): New name (matlab-case-level): Rename to (matlab-case-indent-level): New name (matlab-mode): Doc string tweaks. --- matlab.el | 55 ++++++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/matlab.el b/matlab.el index 88f4aaa..3bf532a 100644 --- a/matlab.el +++ b/matlab.el @@ -7,7 +7,7 @@ ;; Keywords: MATLAB(R) ;; Version: -(defconst matlab-mode-version "4.0" +(defconst matlab-mode-version "5.0" "Current version of MATLAB(R) mode.") ;; @@ -87,7 +87,7 @@ nil (never) means that new *.m files will not enter :group 'matlab :type 'integer) -(defcustom matlab-cont-level 4 +(defcustom matlab-continuation-indent-level 4 "*Basic indentation after continuation if no other methods are found." :group 'matlab :type 'integer) @@ -99,7 +99,7 @@ determining if there is to be continuation is used instead." :group 'matlab :type 'integer) -(defcustom matlab-case-level '(2 . 2) +(defcustom matlab-case-indent-level '(2 . 2) "*How far to indent case/otherwise statements in a switch. This can be an integer, which is the distance to indent the CASE and OTHERWISE commands, and how far to indent commands appearing in CASE @@ -120,7 +120,7 @@ should be ok." "*Whether continuation lines should be aligned to the opening parenthesis. When non-nil, continuation lines are aligned to the opening parenthesis if the opening is not followed by only spaces and ellipses. When nil, continued lines -are simply indented by `matlab-cont-level'." +are simply indented by `matlab-continuation-indent-level'." :group 'matlab :type 'boolean ) @@ -163,7 +163,7 @@ If the value is 'guess, then we guess if a file has end when "Toggle functions-have-end minor mode, indicating function/end pairing." nil (:eval (cond ((eq matlab-functions-have-end 'guess) - " function...?") + " function... ?") ((eq matlab-functions-have-end 'class) " classdef...end") (matlab-functions-have-end @@ -1250,9 +1250,8 @@ with correctly quoted chars. Variables: `matlab-indent-level' Level to indent blocks. - `matlab-cont-level' Level to indent continuation lines. - `matlab-cont-requires-ellipsis' Does your MATLAB support implied elipsis. - `matlab-case-level' Level to unindent case statements. + `matlab-continuation-indent-level' Level to indent after ... continuation + `matlab-case-indent-level' Level to unindent case statements. `matlab-indent-past-arg1-functions' Regexp of functions to indent past the first argument on continuation lines. @@ -1441,7 +1440,6 @@ Return t on success, nil if we couldn't navigate backwards." (let ((ans (matlab-find-prev-line 'ignore-comments))) (when ans (matlab-scan-beginning-of-command) - ;(matlab-beginning-of-command) ans))) (defun matlab-find-prev-line (&optional ignorecomments) @@ -1474,31 +1472,6 @@ If the currnet line is code, return immediately. Ignore comments and whitespace." (forward-comment 100000) (not (eobp))) -;; Iterative version: -;; (catch 'moose -;; (while (or (matlab-ltype-empty) -;; (matlab-ltype-comm)) -;; (when (= 1 (forward-line 1)) -;; (throw 'moose nil))) ;; end of buffer. -;; t)) - - -;;(defvar matlab-in-command-restriction nil -;; "Non-nil if currently in a `matlab-with-current-command' form.") -;; -;;(defmacro omatlab-with-current-command (&rest forms) -;; "Restrict region to the current command and run FORMS. -;;Restore restriction after FORMS run. -;;This command will not add a restriction if we are already -;;restricted." -;; (declare (indent 0) (debug t)) -;; `(save-restriction -;; (when (not matlab-in-command-restriction) -;; (narrow-to-region (matlab-scan-beginning-of-command) -;; (matlab-scan-end-of-command))) -;; (let ((matlab-in-command-restriction t)) -;; ,@forms -;; ))) (defun matlab-valid-end-construct-p () "Return non-nil if the end after point terminates a block. @@ -2580,9 +2553,9 @@ LVL2 is a level 2 scan context with info from previous lines." (matlab-backward-sexp t) (if (not (looking-at "switch\\>")) (error "")) (+ (current-column) - (if (listp matlab-case-level) - (car matlab-case-level) - matlab-case-level))) + (if (listp matlab-case-indent-level) + (car matlab-case-indent-level) + matlab-case-indent-level))) (error (error "Unmatched case/otherwise part"))))) ;; End of a MATRIX ((matlab-line-close-paren-p lvl1) @@ -2691,7 +2664,7 @@ LVL2 is a level 2 scan context with info from previous lines." ;; indentation based on the ;; previous line. (let ((cci (current-indentation))) - (+ cci matlab-cont-level)) + (+ cci matlab-continuation-indent-level)) ;; TODO - this disables indentation MAXs ;; if we really want to be rid of this ;; we can dump a bunch of logic above too. @@ -2708,7 +2681,7 @@ LVL2 is a level 2 scan context with info from previous lines." (while (and (re-search-forward "=" (matlab-point-at-eol) t) (matlab-cursor-in-string-or-comment))) (if (/= (preceding-char) ?=) - (+ ci matlab-cont-level) + (+ ci matlab-continuation-indent-level) (skip-chars-forward " \t") (let ((cc (current-column)) (mi (assoc ?= matlab-maximum-indents))) @@ -2789,8 +2762,8 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (* matlab-indent-level bc) (* matlab-indent-level (or mc 0)) (* matlab-indent-level (- cc)) - (* (if (listp matlab-case-level) - (cdr matlab-case-level) matlab-case-level) + (* (if (listp matlab-case-indent-level) + (cdr matlab-case-indent-level) matlab-case-indent-level) (or ec 0)) )))))) From 400bafe092f17c2ed93a91551b38b99035cab8cd Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 13:48:52 -0400 Subject: [PATCH 088/168] matlab-scan.el: (matlab--mk-keyword-node): Include end pt of found node. (matlab--valid-keyword-node): New matlab-syntax.el: (matlab-move-simple-sexp-backward-internal) (matlab-move-simple-sexp-internal): Make defsubst. matlab.el: (matlab-show-paren-or-block): Re-write how blocks like if ... end are highlighted to use new utils in matlab-scan.el. --- matlab-scan.el | 23 ++++++-- matlab-syntax.el | 4 +- matlab.el | 145 ++++++++++++++++++++++------------------------- 3 files changed, 87 insertions(+), 85 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index ac71d64..2a81cab 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -816,16 +816,16 @@ This function walks down past continuations and open arrays." The elements of the return node are: 0 - type of keyword, like ctrl or decl 1 - text of the keyword - 2 - buffer pos for start of keyword" + 2 - buffer pos for start of keyword + 3 - buffer pos for end of keyword" ;; Don't check the context - assume our callers have vetted this ;; point. This is b/c the search fcns already skip comments and ;; strings for efficiency. (let* ((start (save-excursion (skip-syntax-backward "w_") (point))) - (txt (buffer-substring-no-properties - start - (save-excursion (skip-syntax-forward "w_") (point)))) + (end (save-excursion (skip-syntax-forward "w_") (point))) + (txt (buffer-substring-no-properties start end)) (type (matlab-keyword-p txt))) - (when type (list type txt start) ))) + (when type (list type txt start end) ))) ;;; Valid keyword locations ;; @@ -842,6 +842,19 @@ Returns nil if preceeding non-whitespace char is `.'" nil parent)) +(defun matlab--valid-keyword-node (node &optional parentblock) + "Return non-nil if NODE is in a valid location. +Optional parentblock specifies containing parent block if it is known." + (when node + (save-excursion + (goto-char (nth 2 node)) + (and (matlab--valid-keyword-point) + (cond ((eq (car node) 'mcos) + (matlab--valid-mcos-keyword-point parentblock)) + ((eq (car node) 'arg) + (matlab--valid-arguments-keyword-point parentblock)) + (t t)))))) + (defun matlab--valid-mcos-keyword-point (&optional parentblock) "Return non-nil if at a location that is valid for MCOS keywords. This means that the parent block is a classdef. diff --git a/matlab-syntax.el b/matlab-syntax.el index d848ec7..623c31a 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -568,11 +568,11 @@ This could mean it is: ;;; Syntax Compat functions ;; ;; Left over old APIs. Delete these someday. -(defun matlab-move-simple-sexp-backward-internal (count) +(defsubst matlab-move-simple-sexp-backward-internal (count) "Move backward COUNT number of MATLAB sexps." (forward-sexp (- count))) -(defun matlab-move-simple-sexp-internal(count) +(defsubst matlab-move-simple-sexp-internal(count) "Move over one MATLAB sexp COUNT times. If COUNT is negative, travel backward." (forward-sexp count)) diff --git a/matlab.el b/matlab.el index 3bf532a..2b4d101 100644 --- a/matlab.el +++ b/matlab.el @@ -3432,8 +3432,8 @@ INTERACTIVE is ignored." "Function to assign to `show-paren-data-function'. Highlights parens and if/end type blocks. Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" - (unless (or (matlab-cursor-in-string-or-comment) ; Only do this if not in a string. - (matlab-block-comment-bounds)) + (unless (matlab-cursor-in-string-or-comment) ; Only do this if not in a string. + (save-match-data (save-excursion (let ((here-beg nil) @@ -3497,86 +3497,75 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" ;; word character move back one symbol. This will let ;; us use the block begin / end matchers to figure ;; out where we are. - (when (and (not (eobp)) (not (bobp)) (= (syntax-class here-prev-syntax) 2)) - (forward-symbol -1)) + (let ((startsym (matlab--mk-keyword-node)) + (endsym nil) + ) + (when (matlab--valid-keyword-node startsym) + (goto-char (nth 2 startsym)) - (matlab-navigation-syntax + (condition-case err + (cond + ((eq (car startsym) 'decl) + ;; We are looking at a 'function' start. + ;; Since functions may not have an end, we need + ;; to handle this case special. + (setq here-beg (nth 2 startsym) + here-end (nth 3 startsym)) + (if (matlab--scan-block-forward) + ;; if context is returned, failed to find something + ;; this is a missmatch if fcns don't have end. + (setq mismatch t) + ;; Otherwise, we found something good + (setq endsym (matlab--mk-keyword-node)) + (if (not endsym) + ;; End of buffer on function w/ no end? + (if matlab-functions-have-end + (setq mismatch t) + (setq mismatch nil)) + ;; Found a symbol + (setq there-beg (nth 2 endsym) + there-end (nth 3 endsym) + mismatch nil) + ) )) - (condition-case err - (cond - ((looking-at "function\\>") - ;; We are looking at a 'function' start. Since functions may not have an end, we need - ;; to handle this case special. - (setq here-beg (match-beginning 0) - here-end (match-end 0)) - (matlab-forward-sexp) - (backward-word 1) - (looking-at (concat (matlab-block-end-pre) "\\>")) - (setq there-beg (match-beginning 0) - there-end (match-end 0) - mismatch nil) - ) - ((matlab-cursor-on-valid-block-start) - ;; Above is similar to vvv but with special checks. - ;; Still need to do vvv b/c we need the match-data. - (looking-at (concat (matlab-block-beg-re) "\\>")) - - ;; We are at the beginning of a block. Navigate forward to the end - ;; statement. - (setq here-beg (match-beginning 0) - here-end (match-end 0)) - (matlab-forward-sexp) - (backward-word 1) - (looking-at (concat (matlab-block-end-pre) "\\>")) - (setq there-beg (match-beginning 0) - there-end (match-end 0) - mismatch nil) - ) - ((and (looking-at (concat "\\(" (matlab-block-end-pre) "\\)\\>")) - (matlab-valid-end-construct-p)) - ;; We are at the end of a block. Navigate to the beginning - (setq here-beg (match-beginning 0) - here-end (match-end 0)) - (when (matlab-backward-sexp t t) - (looking-at (concat (matlab-block-beg-re) "\\>")) - (setq there-beg (match-beginning 0) - there-end (match-end 0) - mismatch nil) - )) - ((looking-at (concat (matlab-block-mid-re) "\\>")) - ;; We are at a middle-block expression, like "else" or "catch' - ;; Ideally we'd show the beginning and the end, but lets just show - ;; the beginning. - (setq here-beg (match-beginning 0) - here-end (match-end 0)) - (matlab-backward-sexp t) - (looking-at (concat (matlab-block-beg-re) "\\>")) - (setq there-beg (match-beginning 0) - there-end (match-end 0) - mismatch nil) - ) - - ((looking-at (concat (matlab-endless-blocks-re) "\\>")) - ;; We are at a middle-sub-block expression, like "case" - ;; Ideally we'd show the beginning and the end, but lets just show - ;; the beginning. - (setq here-beg (match-beginning 0) - here-end (match-end 0)) - (matlab-backward-sexp t) - (looking-at (concat (matlab-block-beg-re) "\\>")) - (setq there-beg (match-beginning 0) - there-end (match-end 0) - mismatch nil) - ) - - - ;; No block matches, just return nothing. - (t (setq noreturn t)) - ) - ;; An error occurred. Assume 'here-*' is set, and setup mismatch. - (error (setq mismatch t))) + ;; Misc block starts + ((memq (car startsym) '(ctrl args mcos)) + ;; We are at the beginning of a block. Navigate forward to the end + ;; statement. + (setq here-beg (nth 2 startsym) + here-end (nth 3 startsym)) + (if (matlab--scan-block-forward) + (setq mismatch t) + (setq endsym (matlab--mk-keyword-node)) + (if (not endsym) + (setq mismatch t) + ;; Found a symbol + (setq there-beg (nth 2 endsym) + there-end (nth 3 endsym) + mismatch nil)))) + ;; Misc block middles an ends + ((memq (car startsym) '(end mid case)) + ;; We are at the end of a block or on a middle keyword + ;; like else, catch, or case. In all these go to beginning. + (setq here-beg (nth 2 startsym) + here-end (nth 3 startsym)) + (goto-char here-beg) + (if (matlab--scan-block-backward-up) + (setq mismatch t) + (setq endsym (matlab--mk-keyword-node)) + (if (not endsym) + (setq mismatch t) + ;; Found a symbol + (setq there-beg (nth 2 endsym) + there-end (nth 3 endsym) + mismatch nil)))) + ;; No block matches, just return nothing. + (t (setq noreturn t)) + ) + ;; An error occurred. Assume 'here-*' is set, and setup mismatch. + (error (setq mismatch t)))) ))) (if noreturn From 2171199ddd91e834ed68756f5d72ff0f35bfc2d1 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 14:33:03 -0400 Subject: [PATCH 089/168] matlab.el: (matlab-lattr-implied-continuation): comment out. (matlab-lattr-cont): Replace impl w/ new API and ignore implied continuations. (matlab-find-convenient-line-break): Replace some fcns w/ new versions. (matlab-auto-fill): Replace some fcns w/ new versions. --- matlab.el | 305 +++++++++++++++++++++++++++--------------------------- 1 file changed, 155 insertions(+), 150 deletions(-) diff --git a/matlab.el b/matlab.el index 2b4d101..43a92c1 100644 --- a/matlab.el +++ b/matlab.el @@ -2102,49 +2102,51 @@ Return the symbol 'blockcomm if we are in a block comment." (matlab-line-comment-p (matlab-compute-line-context 1))) ;(save-excursion (matlab-comment-on-line))) -(defun matlab-lattr-implied-continuation () - "Return non-nil if this line has implied continuation on the next. -This is only useful for new versions of MATLAB where ... is optional." - (when (not (matlab-lattr-comm)) - (let ((imp nil)) - (save-excursion - (end-of-line) - (skip-chars-backward " \t") - ;; Test for operator incompleteness. - (setq imp - (/= (point) - ;; Careful, - means range in this expression. - (progn (skip-chars-backward "-+=/*.^&~<>") - (point)))) - (if (not imp) - ;; Test for argument list incompleteness - (condition-case nil - (progn - (end-of-line) - (matlab-up-list -1) - (setq imp (looking-at "("))) - (error nil))) - ) - imp))) +;;(defun matlab-lattr-implied-continuation () +;; "Return non-nil if this line has implied continuation on the next. +;;This is only useful for new versions of MATLAB where ... is optional." +;; (when (not (matlab-lattr-comm)) +;; (let ((imp nil)) +;; (save-excursion +;; (end-of-line) +;; (skip-chars-backward " \t") +;; ;; Test for operator incompleteness. +;; (setq imp +;; (/= (point) +;; ;; Careful, - means range in this expression. +;; (progn (skip-chars-backward "-+=/*.^&~<>") +;; (point)))) +;; (if (not imp) +;; ;; Test for argument list incompleteness +;; (condition-case nil +;; (progn +;; (end-of-line) +;; (matlab-up-list -1) +;; (setq imp (looking-at "("))) +;; (error nil))) +;; ) +;; imp))) (defun matlab-lattr-cont () ; line has continuation "Return non-nil if current line ends in ... and optional comment. If `matlab-cont-requires-ellipsis' is nil, then we need to apply a heuristic to determine if this line would use continuation based on what it ends with." - (let* ((pps (syntax-ppss (point-at-eol))) - (csc (nth 8 pps))) - (or - ;; When the line ends with a comment, it might be an ellipsis. - ;; Ellipsis start has a syntax of 11 (comment-start). - ;; Other comments have high-bit flags, so don't == 11. - (and csc (= (car (syntax-after csc)) 11)) - - ;; If the line doesn't end in ..., but we have optional ..., then - ;; use this annoying heuristic. - (and (null matlab-cont-requires-ellipsis) - (matlab-lattr-implied-continuation)) - ))) + (matlab-line-ellipsis-p (matlab-compute-line-context 1))) + +;; (let* ((pps (syntax-ppss (point-at-eol))) +;; (csc (nth 8 pps))) +;; (or +;; ;; When the line ends with a comment, it might be an ellipsis. +;; ;; Ellipsis start has a syntax of 11 (comment-start). +;; ;; Other comments have high-bit flags, so don't == 11. +;; (and csc (= (car (syntax-after csc)) 11)) +;; +;; ;; If the line doesn't end in ..., but we have optional ..., then +;; ;; use this annoying heuristic. +;; (and (null matlab-cont-requires-ellipsis) +;; (matlab-lattr-implied-continuation)) +;; ))) (defun matlab-prev-line-cont () "Return t if the previous line is a continuation line." @@ -3053,114 +3055,116 @@ unless we decide we can fudge the numbers. Return nil if this line should not be broken. This function will ONLY work on code." ;; First of all, if this is a continuation, then the user is ;; requesting that we don't mess with his stuff. - (if (matlab-lattr-cont) - nil - (save-restriction - (narrow-to-region (matlab-point-at-bol) (matlab-point-at-eol)) - ;; get ourselves onto the fill-column. - (move-to-column fill-column) - (let ((pos nil) - (orig (point))) - (or - ;; Next, if we have a trailing comment, use that. - (progn (setq pos (or (matlab-lattr-comm) (matlab-point-at-bol))) - (goto-char pos) - (if (and (> (current-column) (- fill-column matlab-fill-fudge)) - (< (current-column) (+ fill-column matlab-fill-fudge))) - t - (goto-char orig) - nil)) - ;; Now, lets find the nearest space (after or before fill column) - (let* ((after (save-excursion - (re-search-forward "[ \t]" nil t))) - (before (save-excursion - (re-search-backward "[ \t]" nil t))) - (afterd (- (or after (matlab-point-at-eol)) (point))) - (befored (- (point) (or before (matlab-point-at-bol))))) - ;; Here, if "before" is actually the beginning of our - ;; indentation, then this is most obviously a bad place to - ;; break our lines. - (if before - (save-excursion - (goto-char before) - (if (<= (point) (save-excursion + (let ((lvl1 (matlab-compute-line-context 1))) + (if (matlab-line-ellipsis-p lvl1) ;;(matlab-lattr-cont) + nil + (save-restriction + (narrow-to-region (matlab-point-at-bol) (matlab-point-at-eol)) + ;; get ourselves onto the fill-column. + (move-to-column fill-column) + (let ((pos nil) + (orig (point))) + (or + ;; Next, if we have a trailing comment, use that. + (progn (setq pos (or (matlab-line-comment-p lvl1) ;;(matlab-lattr-comm) + (matlab-point-at-bol))) + (goto-char pos) + (if (and (> (current-column) (- fill-column matlab-fill-fudge)) + (< (current-column) (+ fill-column matlab-fill-fudge))) + t + (goto-char orig) + nil)) + ;; Now, lets find the nearest space (after or before fill column) + (let* ((after (save-excursion + (re-search-forward "[ \t]" nil t))) + (before (save-excursion + (re-search-backward "[ \t]" nil t))) + (afterd (- (or after (matlab-point-at-eol)) (point))) + (befored (- (point) (or before (matlab-point-at-bol))))) + ;; Here, if "before" is actually the beginning of our + ;; indentation, then this is most obviously a bad place to + ;; break our lines. + (if before + (save-excursion + (goto-char before) + (if (<= (point) (save-excursion + (back-to-indentation) + (point))) + (setq before nil)))) + (cond ((and after + (< afterd matlab-fill-fudge) + (< afterd befored)) + (goto-char after) + t) + ((and before + (< befored matlab-fill-fudge) + (< befored afterd)) + (goto-char before) + t) + (t (goto-char orig) + nil))) + ;; Now, lets find the nearest backwards + (progn + (re-search-backward "\\(\\s-\\|\\s.\\)+" nil t) + (while (and (looking-at "\\^\\|\\.\\|'") + (re-search-backward "\\(\\s-\\|\\s.\\)+" nil t))) + (if (or (not (looking-at "\\(\\s-\\|\\s.\\)+")) + (<= (point) (save-excursion (back-to-indentation) - (point))) - (setq before nil)))) - (cond ((and after - (< afterd matlab-fill-fudge) - (< afterd befored)) - (goto-char after) - t) - ((and before - (< befored matlab-fill-fudge) - (< befored afterd)) - (goto-char before) - t) - (t (goto-char orig) - nil))) - ;; Now, lets find the nearest backwards - (progn - (re-search-backward "\\(\\s-\\|\\s.\\)+" nil t) - (while (and (looking-at "\\^\\|\\.\\|'") - (re-search-backward "\\(\\s-\\|\\s.\\)+" nil t))) - (if (or (not (looking-at "\\(\\s-\\|\\s.\\)+")) - (<= (point) (save-excursion - (back-to-indentation) - (point)))) - (progn - ;; We failed in our mission to find anything, or fell - ;; of the edge of the earth. If we are out of - ;; bounds, lets try again. - (goto-char orig) - (if (re-search-backward "\\s.+" nil t) - t - nil)) - ;; Ok, we have a good location to break. Check for column - ;; and ref against nearest list ending to predict a possibly - ;; better break point. - (forward-char 1) - (let ((okpos (current-column)) - (startlst (save-excursion + (point)))) + (progn + ;; We failed in our mission to find anything, or fell + ;; of the edge of the earth. If we are out of + ;; bounds, lets try again. + (goto-char orig) + (if (re-search-backward "\\s.+" nil t) + t + nil)) + ;; Ok, we have a good location to break. Check for column + ;; and ref against nearest list ending to predict a possibly + ;; better break point. + (forward-char 1) + (let ((okpos (current-column)) + (startlst (save-excursion + (condition-case nil + (matlab-up-list -1) + (error nil)) + (if (save-excursion + (forward-char -1) + (looking-at "\\w")) + (forward-word -1)) + (current-column))) + (endlst (save-excursion (condition-case nil - (matlab-up-list -1) + (matlab-up-list 1) (error nil)) - (if (save-excursion - (forward-char -1) - (looking-at "\\w")) - (forward-word -1)) - (current-column))) - (endlst (save-excursion - (condition-case nil - (matlab-up-list 1) - (error nil)) - (current-column)))) - ;; When evaluating list fudge factors, breaking on the - ;; edge of a list, or at the beginning of a function - ;; call can be more valuable than breaking on a symbol - ;; of a mid-sized list. As such, allow double-fudge - ;; for lists. - (cond - ;; First, pick the end of a list. - ((and (< endlst matlab-fill-fudge-hard-maximum) - (<= endlst (+ fill-column matlab-fill-fudge)) - (or (<= (* matlab-fill-fudge 2) (- endlst okpos)) - (<= endlst fill-column)) - (save-excursion - (move-to-column endlst) - (not (looking-at "\\^")))) - (move-to-column endlst) - t) - ;; Else, back up over this list and poke around - ((>= (* 2 matlab-fill-fudge) (- okpos startlst)) - (move-to-column startlst) - t) - ;; Oh well, just do this symbol. - (t (move-to-column okpos) - t))))) - ;; Well, this just sucks - (progn (goto-char orig) - nil)))))) + (current-column)))) + ;; When evaluating list fudge factors, breaking on the + ;; edge of a list, or at the beginning of a function + ;; call can be more valuable than breaking on a symbol + ;; of a mid-sized list. As such, allow double-fudge + ;; for lists. + (cond + ;; First, pick the end of a list. + ((and (< endlst matlab-fill-fudge-hard-maximum) + (<= endlst (+ fill-column matlab-fill-fudge)) + (or (<= (* matlab-fill-fudge 2) (- endlst okpos)) + (<= endlst fill-column)) + (save-excursion + (move-to-column endlst) + (not (looking-at "\\^")))) + (move-to-column endlst) + t) + ;; Else, back up over this list and poke around + ((>= (* 2 matlab-fill-fudge) (- okpos startlst)) + (move-to-column startlst) + t) + ;; Oh well, just do this symbol. + (t (move-to-column okpos) + t))))) + ;; Well, this just sucks + (progn (goto-char orig) + nil))))))) (defun matlab-auto-fill () "Do auto filling. @@ -3174,22 +3178,23 @@ filling which will automatically insert `...' and the end of a line." (move-to-column fill-column) (if (not (bobp)) (forward-char -1)) - (if (matlab-cursor-in-string 'incomplete) + (if (matlab-cursor-in-string) 4 3)) - 0)))) + 0))) + (lvl1 (matlab-compute-line-context 1))) (if (> (current-column) fill-column) (cond - ((matlab-ltype-comm-ignore) + ((matlab-line-comment-ignore-p lvl1) ;;(matlab-ltype-comm-ignore) nil) - ((or (matlab-ltype-comm) + ((or (matlab-line-comment-p lvl1) ;;(matlab-ltype-comm) (and (save-excursion (move-to-column fill-column) (matlab-cursor-in-comment)) - (matlab-lattr-comm))) + (matlab-line-comment-p lvl1))) ;; (matlab-lattr-comm))) ;; If the whole line is a comment, do this. (matlab-set-comm-fill-prefix) (do-auto-fill) (matlab-reset-fill-prefix)) - ((and (matlab-ltype-code) - (not (matlab-lattr-cont)) + ((and (not (or (matlab-line-comment-p lvl1) (matlab-line-empty-p lvl1))) ;;(matlab-ltype-code) + (not (matlab-line-ellipsis-p lvl1)) ;;(matlab-lattr-cont)) matlab-fill-code) ;; If we are on a code line, we ellipsify before we fill. (let ((m (make-marker))) @@ -3199,7 +3204,7 @@ filling which will automatically insert `...' and the end of a line." nil (if (not (save-excursion (forward-char -1) - (matlab-cursor-in-string 'incomplete))) + (matlab-cursor-in-string))) (progn (delete-horizontal-space) (insert " " matlab-elipsis-string "\n") From 40ace8d29039119d9c30e230eed9dd7da68b48b7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 15:09:36 -0400 Subject: [PATCH 090/168] matlab-scan.el: (describe-line-indent-context): Take inputs, and option to return msg instead of display it. matlab.el: (matlab-vers-on-startup, matlab-highlight-block-match-flag) (matlab-show-periodic-code-details-flag): Delete (matlab-frame-init): Remove menu items for things just deleted. (matlab-mode): Only support paren fcn, no more `matlab-enable-block-highlighting'. (matlab-block-highlighter-timer, matlab-enable-block-highlighting) (matlab-highlight-block-match, matlab-block-highlight-*) : Delete (matlab-show-line-info): Misc improvements. --- matlab-scan.el | 24 +++--- matlab.el | 173 +++---------------------------------------- semanticdb-matlab.el | 6 +- 3 files changed, 27 insertions(+), 176 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 2a81cab..a540abe 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1217,14 +1217,16 @@ With arg, enable gathering stats, and flush old stats." (mapc printfcn res))) )))) -(defun matlab-describe-line-indent-context () - "Describe the indentation context for the current line." +(defun matlab-describe-line-indent-context (&optional lvl1 nodisp) + "Describe the indentation context for the current line. +If optional LVL1 is specified, describe that instead of computing. +If optional NODISP, then don't display, just return the msg." (interactive) (back-to-indentation) (let* ((MSG1 "") - (MSG2 "") - (lvl1 (matlab-compute-line-context 1)) - (lvl2 (matlab-compute-line-context 2))) + (lvl1 (or lvl1 (matlab-compute-line-context 1))) + ;(lvl2 (matlab-compute-line-context 2)) + ) (let* ((paren-inner-char (nth mlf-paren-inner-char lvl1)) (open (format "%c" (or paren-inner-char ?\())) (close (format "%c" @@ -1284,13 +1286,13 @@ With arg, enable gathering stats, and flush old stats." "") )) ) - (let* ((lvl2 (matlab-compute-line-context-lvl-2 lvl1)) - ;;(comment - ) +;; (let* ((lvl2 (matlab-compute-line-context-lvl-2 lvl1)) +;; ;;(comment +;; ) - ) - - (message "%s" (concat MSG1 MSG2)) + (if nodisp + MSG1 + (message "%s" MSG1)) )) diff --git a/matlab.el b/matlab.el index 43a92c1..69e8f7b 100644 --- a/matlab.el +++ b/matlab.el @@ -389,31 +389,11 @@ point, but it will be restored for them." :group 'matlab :type 'integer) -;; It is time to disable this. -(defcustom matlab-vers-on-startup nil - "*If non-nil, show the version number on startup." - :group 'matlab - :type 'boolean) - -(defcustom matlab-highlight-block-match-flag t - "*Non-nil means to highlight the matching if/end/whatever. -The highlighting only occurs when the cursor is on a block start or end -keyword." - :group 'matlab - :type 'boolean) - -(defcustom matlab-show-periodic-code-details-flag nil - "*Non-nil means to show code details in the minibuffer. -This will only work if `matlab-highlight-block-match-flag' is non-nil." - :group 'matlab - :type 'boolean) - (defcustom matlab-mode-hook nil "*List of functions to call on entry to MATLAB mode." :group 'matlab :type 'hook) - (defcustom matlab-show-mlint-warnings nil "*If non-nil, show mlint warnings." :group 'matlab @@ -697,10 +677,6 @@ This will only work if `matlab-highlight-block-match-flag' is non-nil." ;; ["Stringify Region" matlab-stringify-region t] ;; ) ("Customize" -; ["Auto Fill Counts Elipsis" -; (lambda () (setq matlab-fill-count-ellipsis-flag -; (not matlab-fill-count-ellipsis-flag))) -; :style toggle :selected 'matlab-fill-count-ellipsis-flag] ["Indent Function Body" (setq matlab-indent-function-body (not (matlab-indent-function-body-p))) :style toggle :selected matlab-indent-function-body] @@ -713,14 +689,6 @@ This will only work if `matlab-highlight-block-match-flag' is non-nil." ["Auto Fill does Code" (setq matlab-fill-code (not matlab-fill-code)) :style toggle :selected matlab-fill-code ] - ["Periodic Code Details" - (setq matlab-show-periodic-code-details-flag - (not matlab-show-periodic-code-details-flag)) - :style toggle :selected matlab-show-periodic-code-details-flag ] - ;; ["Highlight Matching Blocks" - ;; (matlab-enable-block-highlighting) - ;; :style toggle :selected (member 'matlab-start-block-highlight-timer - ;; post-command-hook) ] ["Highlight Cross-Function Variables" matlab-toggle-highlight-cross-function-variables :active (locate-library "mlint") @@ -1264,8 +1232,6 @@ Variables: `matlab-fill-code' Non-nil, auto-fill code in auto-fill-mode. `matlab-fill-strings' Non-nil, auto-fill strings in auto-fill-mode. `matlab-verify-on-save-flag' Non-nil, enable code checks on save. - `matlab-highlight-block-match-flag' - Enable matching block begin/end keywords. `matlab-vers-on-startup' If t, show version on start-up. `matlab-handle-simulink' If t, enable simulink keyword highlighting. @@ -1338,14 +1304,8 @@ All Key Bindings: (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) ;; Parens mode support - (if (and (featurep 'paren) (symbolp 'show-paren-data-function) (symbolp show-paren-data-function)) - (progn - ;; show-paren-mode is nicer than our old thing. - (make-local-variable 'show-paren-data-function) - (setq show-paren-data-function #'matlab-show-paren-or-block) - ) - ;; Enable our own block highlighting if paren mode not around. - (matlab-enable-block-highlighting 1)) + (make-local-variable 'show-paren-data-function) + (setq show-paren-data-function #'matlab-show-paren-or-block) (if window-system (matlab-frame-init)) @@ -1402,7 +1362,7 @@ All Key Bindings: ;; When leaving matlab-mode, turn off mlint (add-hook 'change-major-mode-hook #'matlab-mode-leave) - (if matlab-vers-on-startup (matlab-show-version))) + ) (defun matlab-mode-init-mlint-if-needed () "Check if we should start `mlint-minor-mode' for this buffer." @@ -3577,119 +3537,6 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" nil (list here-beg here-end there-beg there-end mismatch) )))))) - -;;; Block highlighting ======================================================== - -(defvar matlab-block-highlighter-timer nil - "The timer representing the block highlighter.") - -(defun matlab-enable-block-highlighting (&optional arg) - "Start or stop the block highlighter. -Optional ARG is 1 to force enable, and -1 to disable. -If ARG is nil, then highlighting is toggled." - (interactive "P") - (if (not (fboundp 'matlab-run-with-idle-timer)) - (setq matlab-highlight-block-match-flag nil)) - ;; Only do it if it's enabled. - (if (not matlab-highlight-block-match-flag) - nil - ;; Use post command idle hook as a local hook to dissuade too much - ;; cpu time while doing other things. - ;;(make-local-hook 'post-command-hook) - (if (not arg) - (setq arg - (if (member 'matlab-start-block-highlight-timer - post-command-hook) - -1 1))) - (if (> arg 0) - (add-hook 'post-command-hook 'matlab-start-block-highlight-timer nil :local) - (remove-hook 'post-command-hook 'matlab-start-block-highlight-timer :local)))) - -(defvar matlab-block-highlight-overlay nil - "The last highlighted overlay.") -(make-variable-buffer-local 'matlab-block-highlight-overlay) - -(defvar matlab-block-highlight-timer nil - "Last started timer.") -(make-variable-buffer-local 'matlab-block-highlight-timer) - -(defun matlab-start-block-highlight-timer () - "Set up a one-shot timer if we are in MATLAB mode." - (if (eq major-mode 'matlab-mode) - (progn - (if matlab-block-highlight-overlay - (unwind-protect - (matlab-delete-overlay matlab-block-highlight-overlay) - (setq matlab-block-highlight-overlay nil))) - (if matlab-block-highlight-timer - (unwind-protect - (matlab-cancel-timer matlab-block-highlight-timer) - (setq matlab-block-highlight-timer nil))) - (setq matlab-block-highlight-timer - (matlab-run-with-idle-timer - 1 nil 'matlab-highlight-block-match - (current-buffer)))))) - -(defun matlab-highlight-block-match (&optional buff-when-launched) - "Highlight a matching block if available. -BUFF-WHEN-LAUNCHED is the buffer that was active when the timer was set." - (setq matlab-block-highlight-timer nil) - (if (null buff-when-launched) - ;; We were passed a null. This indicates an old version of XEmacs - ;; so just turn the feature off - (setq matlab-highlight-block-match-flag nil) - ;; Only do neat stuff in the same buffer as the one we were - ;; initialized from. - (when (and buff-when-launched - (eq buff-when-launched (current-buffer))) - (let ((inhibit-quit nil) ;turn on G-g - (matlab-scan-on-screen-only t)) - (if matlab-show-periodic-code-details-flag - (matlab-show-line-info)) - (if (not (matlab-cursor-in-string-or-comment)) - (save-excursion - (if (or (bolp) - (looking-at "\\s-") - (save-excursion (forward-char -1) (looking-at "\\s-"))) - nil - (forward-word -1)) - (if (and (looking-at (concat (matlab-block-beg-re) "\\>")) - (not (looking-at "function"))) - (progn - ;; We scan forward... - (matlab-forward-sexp) - (backward-word 1) - (if (not (looking-at matlab-block-end-pre-if)) - nil ;(message "Unterminated block, or end off screen.") - (setq matlab-block-highlight-overlay - (matlab-make-overlay (point) - (progn (forward-word 1) - (point)) - (current-buffer))) - (matlab-overlay-put matlab-block-highlight-overlay - 'face 'matlab-region-face))) - (if (and (looking-at (concat (matlab-block-end-pre) "\\>")) - (not (looking-at "function")) - (matlab-valid-end-construct-p)) - (progn - ;; We scan backward - (forward-word 1) - (condition-case nil - (progn - (matlab-backward-sexp) - (if (not (looking-at (matlab-block-beg-re))) - nil ;(message "Unstarted block at cursor.") - (setq matlab-block-highlight-overlay - (matlab-make-overlay (point) - (progn (forward-word 1) - (point)) - (current-buffer))) - (matlab-overlay-put matlab-block-highlight-overlay - 'face 'matlab-region-face))) - (error (message "Unstarted block at cursor.")))) - ;; do nothing - )))))))) - ;;; M Block Folding with hideshow ============================================= @@ -4074,20 +3921,24 @@ desired. Optional argument FAST is not used." "Display type and attributes of current line. Used in debugging." (interactive) (matlab-navigation-syntax - (let* ((msg "line-info:") + (let* ((msg "") (lvl2 (matlab-compute-line-context 2)) + (lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) + (lvl1msg (matlab-describe-line-indent-context lvl1 t)) (indent nil) (fullindent (matlab--calc-indent lvl2 'indent)) (nexti (matlab-next-line-indentation (matlab-previous-line lvl2)))) (setq msg (concat msg - " Line type: " (symbol-name (car indent)) - " This Line: " (int-to-string (nth 1 indent)) - " Next Line: " (int-to-string nexti))) + "Line Syntax: " lvl1msg + " | Preferred Indents: This: " (int-to-string (nth 1 indent)) + " Next: " (int-to-string nexti) + )) + (if (matlab-lattr-cont) (setq msg (concat msg " w/cont"))) (if (matlab-lattr-comm) (setq msg (concat msg " w/comm"))) - (message msg)))) + (message "%s" msg)))) (provide 'matlab) diff --git a/semanticdb-matlab.el b/semanticdb-matlab.el index 9fe973c..ec56fd0 100644 --- a/semanticdb-matlab.el +++ b/semanticdb-matlab.el @@ -337,8 +337,7 @@ Return a list of tags." (let ((files (semanticdb-matlab-find-name regex 'regex))) (delq nil (mapcar #'(lambda (x) - (let ((matlab-vers-on-startup nil)) - (car (semanticdb-file-stream x)))) + (car (semanticdb-file-stream x))) files))))) (cl-defmethod semanticdb-find-tags-for-completion-method @@ -378,8 +377,7 @@ Returns a table of all matching tags." ;; generate tags (delq nil (mapcar #'(lambda (x) - (let ((matlab-vers-on-startup nil)) - (car (semanticdb-file-stream x)))) + (car (semanticdb-file-stream x))) compdb))))) (provide 'semanticdb-matlab) From b21055580f8f5759ad91f0e7731a95e33864078f Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 15:10:58 -0400 Subject: [PATCH 091/168] matlab-maint.el: (matlab-maint-mode-map): Add a symref and show info binding. (matlab-maint-show-info, matlab-maint-symref-this): New --- matlab-maint.el | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/matlab-maint.el b/matlab-maint.el index 1658219..11ae674 100644 --- a/matlab-maint.el +++ b/matlab-maint.el @@ -24,6 +24,8 @@ (require 'matlab) (require 'matlab-shell) (require 'matlab-netshell) +(require 'semantic/symref) +(require 'semantic/symref/list) ;;; Code: @@ -31,8 +33,13 @@ ;; (defvar matlab-maint-mode-map (let ((km (make-sparse-keymap))) + ;; compile debug cycle (define-key km [f8] 'matlab-maint-run-tests) (define-key km [f9] 'matlab-maint-compile-matlab-emacs) + ;; coding + (define-key km [f7] 'matlab-maint-symref-this) + ;; matlab + (define-key km [f5] 'matlab-maint-show-info) km) "Keymap used by matlab mode maintainers.") @@ -67,7 +74,34 @@ (matlab-maint-minor-mode 1)))) ) -;;; Commands +;;; Testing stuff in M buffers +;; +(defun matlab-maint-show-info () + "Show info about line in current matlab buffer." + (interactive) + (when (eq major-mode 'matlab-mode) + (matlab-show-line-info) + )) + +;;; HACKING ELISP +;; + +(defun matlab-maint-symref-this () + "Open a symref buffer on symbol under cursor." + (interactive) + (semantic-fetch-tags) + (let ((ct (semantic-current-tag))) + ;; Must have a tag... + (when (not ct) (error "Place cursor inside tag to be searched for")) + ;; Gather results and tags + (message "Gathering References for %s.." (semantic-tag-name ct)) + (let* ((name (semantic-tag-name ct)) + (res (semantic-symref-find-references-by-name name))) + (semantic-symref-produce-list-on-results res name) + (semantic-symref-list-expand-all) ))) + + +;;; COMPILE AND TEST ;; ;; Helpful commands for maintainers. (defcustom matlab-maint-compile-opts '("emacs" "emacs24" "emacs25" "emacs26") @@ -123,6 +157,8 @@ With universal ARG, ask for the code to be run with output tracking turned on." (delete-other-windows) (goto-char (point-max))) +;;; MATLAB SHELL tools +;; (defun matlab-maint-set-buffer-to (file) "Set the current buffer to FILE found in matlab-mode's source. From 9ff2d09342dfc9797e6543f4d2c934375079d55b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 16:34:34 -0400 Subject: [PATCH 092/168] matlab-scan.el: (matlab-end-of-comment-point): New (matlab-line-code-p): Fix doc. (matlab-line-boring-code-p): New (matlab-line-end-of-code): New defubst (matlab-line-end-of-code-needs-semicolon-p): New matlab-shell: (matlab-read-line-at-point): replace cmd grab with simpler new version. (matlab-shell-run-cell): Stop using obsolete input to matlab-end-of-command. matlab.el: (matlab-region-face): Get rid of old compatibility code. (matlab-function-font-lock-keywords): Use new end-of-command fcn. (matlab-beginning-of-command): Delete old impl, use new only. (matlab-lattr-implied-continuation) (matlab-prev-line-cont, matlab-lattr-array-cont) (matlb-lattr-array-end, matlab-lattr-middle-block-cont) (matlab-lattr-endless-block-cont, matlab-lattr-local-end): DELETE (matlab-comment-on-line): Simpler new implementation (matlab-calculate-indentation-1): Replace some old calls with new versions. (matlab-indent-end-before-ret): Replace regex w/ new keyword handler. (matlab-semicolon-on-return): Replace impl w/ new semicolon api (matlab-fill-paragraph): Replace old api calls w/ new. (matlab-mode-vf-quiesce-buffer): Replace lots of old parsing code with simple calls from new api. --- matlab-scan.el | 32 ++++- matlab-shell.el | 15 +-- matlab.el | 330 ++++++------------------------------------------ 3 files changed, 76 insertions(+), 301 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index a540abe..d63f3f9 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -351,6 +351,12 @@ All lines that start with a comment end with a comment." (goto-char (nth mlf-end-comment-pt lvl1)) (current-column)))) +(defsubst matlab-line-end-comment-point (lvl1) + "Return star of comment on line, or nil if no comment. +All lines that start with a comment end with a comment." + (when (eq (nth mlf-end-comment-type lvl1) 'comment) + (nth mlf-end-comment-pt lvl1))) + (defsubst matlab-line-ellipsis-p (lvl1) "Return if this line ends with a comment." (eq (nth mlf-end-comment-type lvl1) 'ellipsis)) @@ -368,9 +374,13 @@ All lines that start with a comment end with a comment." ;; Code and Declarations (defsubst matlab-line-code-p (lvl1) - "Return t if the current line is boring old code." + "Return t if the current line is code." (eq (car lvl1) 'code)) +(defsubst matlab-line-boring-code-p (lvl1) + "Return t if the current line is boring old code." + (and (eq (car lvl1) 'code) (not (nth 1 lvl1)))) + (defsubst matlab-line-block-start-keyword-p (lvl1) "Return t if the current line starts with block keyword." (eq (car lvl1) 'block-start)) @@ -394,7 +404,7 @@ These are keywords like `else' or `catch'." These are keywords like `else' or `catch'." (and (eq (car lvl1) 'block-start) (eq (nth 1 lvl1) 'case))) -(defsubst matlab-line-end-of-code (&optional lvl1) +(defun matlab-line-end-of-code (&optional lvl1) "Go to the end of the code on the current line. If there is a comment or ellipsis, go to the beginning of that. If the line starts with a comment return nil, otherwise t." @@ -407,6 +417,24 @@ If the line starts with a comment return nil, otherwise t." (goto-char (nth mlf-end-comment-pt lvl1)) (goto-char (point-at-eol))))) +(defun matlab-line-end-of-code-needs-semicolon-p (&optional lvl1) + "Return non-nil of this line of code needs a semicolon. +Move cursor to where the ; should be inserted. +Return nil for empty and comment only lines." + (unless lvl1 (setq lvl1 (matlab-compute-line-context 1))) + (let ((endpt nil)) + (save-excursion + (when (and (not (matlab-beginning-of-outer-list)) + (matlab-line-boring-code-p lvl1) + (matlab-line-end-of-code lvl1)) + (skip-syntax-backward " ") + (when (and (not (matlab-cursor-in-string-or-comment)) + (not (= (preceding-char) ?\;))) + (setq endpt (point))) + )) + (when endpt + (goto-char endpt)))) + ;; Parenthetical blocks (defsubst matlab-line-close-paren-p (lvl1) "Non nil If the current line starts with closing paren (any type.)" diff --git a/matlab-shell.el b/matlab-shell.el index f00566b..35959e1 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -1597,16 +1597,9 @@ Snatched and hacked from dired-x.el" (search-forward-regexp comint-prompt-regexp) (buffer-substring (point) (matlab-point-at-eol))))) (save-excursion - ;; In matlab buffer, find all the text for a command. - ;; so back over until there is no more continuation. - (while (save-excursion (forward-line -1) (matlab-lattr-cont)) - (forward-line -1)) - ;; Go forward till there is no continuation - (beginning-of-line) - (let ((start (point))) - (while (matlab-lattr-cont) (forward-line 1)) - (end-of-line) - (buffer-substring start (point)))))) + (buffer-substring-no-properties + (matlab-scan-beginning-of-command) + (matlab-scan-end-of-command))))) (defun matlab-non-empty-lines-in-string (str) "Return number of non-empty lines in STR." @@ -2202,7 +2195,7 @@ Similar to `comint-send-input'." (error "You are not in a cell. Try `matlab-shell-save-and-go' instead")) (when (matlab-ltype-comm) ;; Skip over starting comment from the current cell. - (matlab-end-of-command 1) + (matlab-end-of-command) (end-of-line) (forward-char 1)) (point))) diff --git a/matlab.el b/matlab.el index 69e8f7b..ab7eb53 100644 --- a/matlab.el +++ b/matlab.el @@ -422,24 +422,10 @@ point, but it will be restored for them." (make-variable-buffer-local 'matlab-change-current-directory) -;; Load in the region we use for highlighting stuff. -(if (and (featurep 'custom) (fboundp 'custom-declare-variable)) - - (let ((l-region-face (if (facep 'region) 'region 'zmacs-region))) - ;; If we have custom, we can make our own special face like this - (defface matlab-region-face - (list - (list t - (list :background (face-background l-region-face) - :foreground (face-foreground l-region-face)))) - "*Face used to highlight a matlab region." - :group 'matlab)) - - ;; If we do not, then we can fake it by copying 'region. - (cond ((facep 'region) - (copy-face 'region 'matlab-region-face)) - (t - (copy-face 'zmacs-region 'matlab-region-face)))) +(defface matlab-region-face + '((t :inherit region)) + "*Face used to highlight a matlab region." + :group 'matlab) (defvar matlab-unterminated-string-face 'matlab-unterminated-string-face "Self reference for unterminated string face.") @@ -494,14 +480,10 @@ point, but it will be restored for them." :bold t)) "*Face to use for cellbreak %% lines.") - -;;; MATLAB mode variables ===================================================== - -;; abbrev table (defvar matlab-mode-abbrev-table nil "The abbrev table used in `matlab-mode' buffers.") - (define-abbrev-table 'matlab-mode-abbrev-table ()) + ;;; Keybindings =============================================================== @@ -538,7 +520,6 @@ point, but it will be restored for them." (define-key km [(control j)] 'matlab-linefeed) (define-key km "\M-\r" 'newline) (define-key km [(meta \;)] 'matlab-comment) - ;;(define-key km [(meta q)] 'matlab-fill-paragraph) ; replace w/ fill-paragraph-function setting. (define-key km [(meta a)] 'matlab-beginning-of-command) (define-key km [(meta e)] 'matlab-end-of-command) (define-key km [(meta j)] 'matlab-comment-line-break-function) @@ -1003,7 +984,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." "\\(\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*=[ \t\n.]*\\)?" "\\(?:[sg]et\\.\\)?\\sw+\\s-*(") '("\\s-*\\(\\sw+\\)\\s-*[,)]" - (save-excursion (matlab-end-of-command) (point)) + (save-excursion (matlab-scan-end-of-command)) nil (1 font-lock-variable-name-face))) ;; I like variables for FOR loops @@ -1921,68 +1902,17 @@ If `matlab-functions-have-end', skip over functions with end." (goto-char (match-end 0)) (current-word))))) -(defun matlab-beginning-of-command (&optional arg) +(defun matlab-beginning-of-command () "Go to the beginning of an M command. Travels across continuations." (interactive "P") - (if t - (matlab-scan-beginning-of-command) - - ;;; TODO - delete this old impl - (beginning-of-line) - (save-match-data - (let* ((lvl1 (matlab-compute-line-context 1)) - (p nil) - (bc (matlab-line-block-comment-start lvl1)) ;(matlab-block-comment-bounds))) - ) - ;; block comment - just go to the beginning. - (if bc - (goto-char bc) - - ;; ELSE : Scan across lines that are related. - ;; Step one, skip all comments indented as continutions of a previous. - ;; Using forward-comment is very fast, and just skipps all comments until - ;; we hit a line of code. - ;; NOTE: This may fail with poorly indented code. - (when (or (and (matlab-last-guess-decl-p) - (matlab-scan-comment-help-p lvl1)) ;(matlab-ltype-help-comm) - (matlab-ltype-continued-comm)) - (forward-comment -100000)) - - ;; Now walk backward across continued code lines. - (while (and (or (setq p (matlab-line-close-paren-outer-point (matlab-compute-line-context 1))) ;;(matlab-lattr-array-cont)) ;; do this first b/c fast - (matlab-scan-previous-line-ellipsis-p) ;; (matlab-prev-line-cont) - ;; We used to do this, now handled w/ forward-comment above. - ;;(matlab-ltype-continued-comm) - ) - (save-excursion (beginning-of-line) (not (bobp)))) - (if p (goto-char p) (matlab-prev-line)) - (setq p nil))) - (back-to-indentation))))) + (matlab-scan-beginning-of-command)) (defun matlab-end-of-command () "Go to the end of an M command. Travells a cross continuations" (interactive) - (while (and (or (matlab-lattr-cont) - (save-excursion - (forward-line 1) - (or (matlab-lattr-array-cont) - (matlab-ltype-continued-comm)))) - ;; This hack is a short circuit. If a user did not - ;; correctly end a matrix, this will short-circuit - ;; as soon as something that would never appear in a matrix - ;; becomes visible. - (not (save-excursion - (beginning-of-line) - (and (looking-at (matlab-block-scan-re)) - (not (looking-at (matlab-match-function-re)))))) - ;; If we hit the end of the buffer unexpectedly, this test - ;; will fail and we'll avoid looping forever. (E.g., this - ;; is triggered if a continuation line is the last one in - ;; the buffer, and the line lacks the final newline.) - (zerop (forward-line 1)))) - (end-of-line)) + (matlab-scan-end-of-command)) ;;; Line types, attributes, and string/comment context ================================================= @@ -2055,81 +1985,10 @@ Return the symbol 'blockcomm if we are in a block comment." "Return t if current line is a MATLAB code line." (let ((lvl (matlab-compute-line-context 1))) (not (or (matlab-line-comment-p lvl) (matlab-line-empty-p lvl))))) - ;(and (not (matlab-ltype-empty)) (not (matlab-ltype-comm)))) (defun matlab-lattr-comm () ; line has comment "Return t if current line contain a comment." (matlab-line-comment-p (matlab-compute-line-context 1))) - ;(save-excursion (matlab-comment-on-line))) - -;;(defun matlab-lattr-implied-continuation () -;; "Return non-nil if this line has implied continuation on the next. -;;This is only useful for new versions of MATLAB where ... is optional." -;; (when (not (matlab-lattr-comm)) -;; (let ((imp nil)) -;; (save-excursion -;; (end-of-line) -;; (skip-chars-backward " \t") -;; ;; Test for operator incompleteness. -;; (setq imp -;; (/= (point) -;; ;; Careful, - means range in this expression. -;; (progn (skip-chars-backward "-+=/*.^&~<>") -;; (point)))) -;; (if (not imp) -;; ;; Test for argument list incompleteness -;; (condition-case nil -;; (progn -;; (end-of-line) -;; (matlab-up-list -1) -;; (setq imp (looking-at "("))) -;; (error nil))) -;; ) -;; imp))) - -(defun matlab-lattr-cont () ; line has continuation - "Return non-nil if current line ends in ... and optional comment. -If `matlab-cont-requires-ellipsis' is nil, then we need to apply -a heuristic to determine if this line would use continuation -based on what it ends with." - (matlab-line-ellipsis-p (matlab-compute-line-context 1))) - -;; (let* ((pps (syntax-ppss (point-at-eol))) -;; (csc (nth 8 pps))) -;; (or -;; ;; When the line ends with a comment, it might be an ellipsis. -;; ;; Ellipsis start has a syntax of 11 (comment-start). -;; ;; Other comments have high-bit flags, so don't == 11. -;; (and csc (= (car (syntax-after csc)) 11)) -;; -;; ;; If the line doesn't end in ..., but we have optional ..., then -;; ;; use this annoying heuristic. -;; (and (null matlab-cont-requires-ellipsis) -;; (matlab-lattr-implied-continuation)) -;; ))) - -(defun matlab-prev-line-cont () - "Return t if the previous line is a continuation line." - (save-excursion (and (if (= -1 (forward-line -1)) nil t) - (matlab-lattr-cont)))) - -(defun matlab-lattr-array-cont (&optional lvl2) - "Return non-nil if current line is in an array. -If the entirety of the array is on this line, return nil." - (matlab-line-close-paren-outer-point (matlab-get-lvl1-from-lvl2 lvl2))) -;; (condition-case nil -;; (save-excursion -;; (beginning-of-line) -;; (matlab-up-list -1) -;; (and (looking-at "[[{]") (point))) -;; (error nil))) - -(defun matlab-lattr-array-end () - "Return non-nil if the current line closes an array. -by close, the first character is the end of an array." - (save-excursion - (back-to-indentation) - (and (looking-at "[]}]") (matlab-lattr-array-cont)))) (defun matlab-lattr-block-open (&optional eol) "Return a number representing the number of unterminated block constructs. @@ -2163,33 +2022,6 @@ Optional EOL indicates a virtual end of line." (setq v (1- v)))))) v)))) -;;(defun matlab-lattr-middle-block-cont () -;; "Return the number of middle block continuations. -;;This should be 1 or nil, and only true if the line starts with one of these -;;special items." -;; (save-excursion -;; (back-to-indentation) -;; (if (looking-at (concat (matlab-block-mid-re) "\\>")) -;; (if (and (re-search-forward (matlab-block-end-pre) -;; (matlab-point-at-eol) -;; t) -;; (matlab-valid-end-construct-p)) -;; ;; If there is an END, we still need to return non-nil, -;; ;; but the number value is a net of 0. -;; 0 -;; 1) -;; nil))) - -(defun matlab-lattr-endless-block-cont () - "Return the number of middle block continuations. -This should be 1 or nil, and only true if the line starts with one of these -special items." - (save-excursion - (back-to-indentation) - (if (looking-at (concat (matlab-endless-blocks-re) "\\>")) - 1 - nil))) - (defun matlab-lattr-block-close (&optional start) "Return the number of closing block constructs on this line. Argument START is where to start searching from." @@ -2220,22 +2052,8 @@ Argument START is where to start searching from." (goto-char nomove) (setq v (1- v))) )) - ;; If we can't scoot back, do a cheat-test to see if there - ;; is a matching else or elseif. - ;;(goto-char bound) - ;;(back-to-indentation) - ;;(if (looking-at (matlab-block-mid-re)) - ;; (setq v (1- v))) - ;; Return nil, or a number (if (<= v 0) 0 v))))) -;;(defun matlab-lattr-local-end () -;; "Return t if this line begins with an end construct." -;; (save-excursion -;; (back-to-indentation) -;; (and (looking-at (concat "\\<" (matlab-block-end-re) "\\>")) -;; (matlab-valid-end-construct-p)))) - (defun matlab-function-called-at-point () "Return a string representing the function called nearby point." (save-excursion @@ -2252,17 +2070,9 @@ Argument START is where to start searching from." "Place the cursor on the beginning of a valid comment on this line. If there isn't one, then return nil, point otherwise." (interactive) - (let ((eol (matlab-point-at-eol)) - (p (point)) - (signal-error-on-buffer-boundary nil)) - (beginning-of-line) - (while (and (re-search-forward "%" eol t) - (save-excursion (forward-char -1) (matlab-cursor-in-string t)))) - (if (not (bolp)) (forward-char -1)) - (if (and (looking-at "%") (not (matlab-cursor-in-string t))) - (point) - (goto-char p) - nil))) + (let ((lvl1 (matlab-compute-line-context 1))) + (goto-char (or (matlab-line-end-comment-point lvl1) + (point))))) ;;; Indent functions ========================================================== @@ -2449,7 +2259,6 @@ LVL2 is a level 2 scan context with info from previous lines." (list 'function ci)) ;; END keyword ((matlab-line-end-p lvl1) - ;;(matlab-lattr-local-end) (let* ((CTXT (matlab-with-context-line lvl1 (matlab-scan-block-start-context)))) @@ -2468,34 +2277,9 @@ LVL2 is a level 2 scan context with info from previous lines." ;; end is matched, and whether the line is blank up to the match. ;; If so, return the indentation of the match. (setq ci (matlab-line-indentation (nth 3 CTXT))))) - ;;(catch 'indent - ;; (save-excursion - ;; (when (progn (beginning-of-line) - ;; (and (looking-at "[ \t]*end\\b") - ;; (matlab-backward-sexp t t))) - ;; (let ((match (point))) - ;; (beginning-of-line) - ;; (looking-at "[ \t]*") - ;; (when (= match (match-end 0)) - ;; (let ((match-col-end - ;; (save-excursion - ;; (goto-char match) - ;; (current-column))) - ;; (match-col-beginning - ;; (save-excursion - ;; (goto-char (match-beginning 0)) - ;; (current-column))) - ;; ) - ;; (setq ci (- match-col-end match-col-beginning))) - ;; (throw 'indent nil))))) - ;; ;; End of special case for end and match after "^[ \t]*". - ;; (setq ci (+ ci - ;; (* (1- (matlab-lattr-block-open (point))) - ;; matlab-indent-level)))))) (list 'blockend ci)) ;; ELSE/CATCH keywords ((matlab-line-block-middle-p lvl1) - ;(matlab-lattr-middle-block-cont) (let ((m (match-string 1))) (list 'blockmid (condition-case nil @@ -2507,7 +2291,6 @@ LVL2 is a level 2 scan context with info from previous lines." (error (error "Unmatched %s" m)))))) ;; CASE/OTHERWISE keywords ((matlab-line-block-case-p lvl1) - ;;(matlab-lattr-endless-block-cont) (list 'blockendless (condition-case nil (save-excursion @@ -2521,8 +2304,7 @@ LVL2 is a level 2 scan context with info from previous lines." (error (error "Unmatched case/otherwise part"))))) ;; End of a MATRIX ((matlab-line-close-paren-p lvl1) - ;;(matlab-lattr-array-end) - (list 'array-end (let* ((fc (matlab-line-close-paren-inner-char lvl1)) ;;following-char)) + (list 'array-end (let* ((fc (matlab-line-close-paren-inner-char lvl1)) (pc (matlab-line-close-paren-inner-col lvl1)) (mi (assoc fc matlab-maximum-indents)) (max (if mi (if (listp (cdr mi)) @@ -2536,8 +2318,8 @@ LVL2 is a level 2 scan context with info from previous lines." (1- ind) ; decor pc)))) ;; Code lines - ((and (not (matlab-lattr-array-cont)) - (not (matlab-prev-line-cont))) + ((and (not (matlab-line-close-paren-outer-point lvl1)) + (not (matlab-scan-previous-line-ellipsis-p))) ;; Old check for base code line was using ;; 'matlab-beginning-of-command' which does a lot of work, like @@ -2556,7 +2338,7 @@ LVL2 is a level 2 scan context with info from previous lines." ;; Lines continued from previous statements. (t (list (if (matlab-line-empty-p lvl1) 'empty - (if (matlab-lattr-array-cont) 'array-cont 'code)) + (if (matlab-line-close-paren-outer-point lvl1) 'array-cont 'code)) ;; Record beginning of the command (let* ((ci-local ci) ;; we prob want to change this due to continuation. (boc (save-excursion @@ -2764,13 +2546,12 @@ Must be one of: (defun matlab-indent-end-before-ret () "Indent line if block end, start new line, and indent again." (interactive) - (if (save-excursion - (beginning-of-line) - (looking-at (concat "^\\s-*\\(" (matlab-block-end-re) - "\\|" (matlab-block-mid-re) - "\\|" (matlab-endless-blocks-re) - "\\|function\\)"))) - (matlab-indent-line)) + (let ((lvl1 (matlab-compute-line-context 1))) + (when (or (matlab-line-end-p lvl1) + (matlab-line-block-case-p lvl1) + (matlab-line-block-middle-p lvl1) + (matlab-line-declaration-p lvl1)) + (matlab-indent-line))) (newline) (matlab-indent-line)) @@ -2778,20 +2559,9 @@ Must be one of: (defun matlab-semicolon-on-return () "If needed, add a semicolon at point automatically." (if matlab-return-add-semicolon - (if (and (not (matlab-ltype-empty)) - (not (save-excursion - (skip-chars-backward " \t;" (matlab-point-at-bol)) - (looking-at "\\s-*;"))) - (save-excursion - (let ((p (point))) - (matlab-end-of-command) - (eq p (point)))) - (save-excursion - (matlab-beginning-of-command) - ;; Note: Compile warning below, but defined later. - (not (looking-at matlab-quiesce-nosemi-regexp)))) - (insert ";")) - )) + (when (matlab-line-end-of-code-needs-semicolon-p) + (insert ";") + (end-of-line)))) (defun matlab-indent-before-ret () "Indent line, start new line, and indent again." @@ -3016,7 +2786,7 @@ not be broken. This function will ONLY work on code." ;; First of all, if this is a continuation, then the user is ;; requesting that we don't mess with his stuff. (let ((lvl1 (matlab-compute-line-context 1))) - (if (matlab-line-ellipsis-p lvl1) ;;(matlab-lattr-cont) + (if (matlab-line-ellipsis-p lvl1) nil (save-restriction (narrow-to-region (matlab-point-at-bol) (matlab-point-at-eol)) @@ -3144,17 +2914,17 @@ filling which will automatically insert `...' and the end of a line." (lvl1 (matlab-compute-line-context 1))) (if (> (current-column) fill-column) (cond - ((matlab-line-comment-ignore-p lvl1) ;;(matlab-ltype-comm-ignore) + ((matlab-line-comment-ignore-p lvl1) nil) - ((or (matlab-line-comment-p lvl1) ;;(matlab-ltype-comm) + ((or (matlab-line-comment-p lvl1) (and (save-excursion (move-to-column fill-column) (matlab-cursor-in-comment)) - (matlab-line-comment-p lvl1))) ;; (matlab-lattr-comm))) + (matlab-line-comment-p lvl1))) ;; If the whole line is a comment, do this. (matlab-set-comm-fill-prefix) (do-auto-fill) (matlab-reset-fill-prefix)) - ((and (not (or (matlab-line-comment-p lvl1) (matlab-line-empty-p lvl1))) ;;(matlab-ltype-code) - (not (matlab-line-ellipsis-p lvl1)) ;;(matlab-lattr-cont)) + ((and (not (or (matlab-line-comment-p lvl1) (matlab-line-empty-p lvl1))) + (not (matlab-line-ellipsis-p lvl1)) matlab-fill-code) ;; If we are on a code line, we ellipsify before we fill. (let ((m (make-marker))) @@ -3299,7 +3069,7 @@ ARG is passed to `fill-paragraph' and will justify the text." (interactive "P") (cond ((or (matlab-ltype-comm) (and (matlab-cursor-in-comment) - (not (matlab-lattr-cont)))) + (not (matlab-line-ellipsis-p (matlab-compute-line-context 1))))) ;; We are in a comment, lets fill the paragraph with some ;; nice regular expressions. ;; Cell start/end markers of %% also separate paragraphs @@ -3312,8 +3082,7 @@ ARG is passed to `fill-paragraph' and will justify the text." (forward-char 1))) (beginning-of-line) (point))) - (end (save-excursion (matlab-end-of-command) - (point))) + (end (save-excursion (matlab-scan-end-of-command))) (fill-prefix nil)) (matlab-set-comm-fill-prefix) (save-restriction @@ -3326,19 +3095,18 @@ ARG is passed to `fill-paragraph' and will justify the text." ;; completely refill it using the smart line breaking code. (save-restriction (narrow-to-region (save-excursion - (matlab-beginning-of-command) + (matlab-scan-beginning-of-command) (beginning-of-line) (point)) (save-excursion - (matlab-end-of-command) - (point))) + (matlab-scan-end-of-command))) ;; Remove all line breaks (goto-char (point-min)) (while (and (re-search-forward "$" nil t) (not (eobp))) (delete-horizontal-space) ;; Blow away continuation marks - (if (matlab-lattr-cont) + (if (matlab-line-ellipsis-p (matlab-compute-line-context 1)) (progn (goto-char (match-beginning 0)) (forward-char 1) @@ -3891,25 +3659,11 @@ desired. Optional argument FAST is not used." (setq msgpos (+ msgpos dir)) (if (or (> msgpos 5) (< msgpos 0)) (setq dir (- dir) msgpos (+ (* 2 dir) msgpos))) - (matlab-end-of-command) - (if (matlab-cursor-in-comment) - (progn - (matlab-comment-on-line) - (skip-chars-backward " \t"))) - (if (and (not (= (preceding-char) ?\;)) - (not (matlab-cursor-in-string t)) - (not (save-excursion - (beginning-of-line) - (looking-at matlab-quiesce-nosemi-regexp)))) - (let ((p (point))) - (skip-chars-backward " \t") - (if (/= p (point)) - (progn - (delete-region p (point)) - (forward-line -1)) - (if (matlab-mode-highlight-ask (point) (+ 1 (point)) - "Add Semi colon here? ") - (insert ";"))))) + (matlab-scan-end-of-command) + (when (and (matlab-line-end-of-code-needs-semicolon-p) + (matlab-mode-highlight-ask (point) (+ 1 (point)) + "Add Semi colon here? ")) + (insert ";")) (forward-line 1)))) (message "Scanning .... done")) @@ -3934,7 +3688,7 @@ desired. Optional argument FAST is not used." " Next: " (int-to-string nexti) )) - (if (matlab-lattr-cont) + (if (matlab-line-ellipsis-p lvl1) (setq msg (concat msg " w/cont"))) (if (matlab-lattr-comm) (setq msg (concat msg " w/comm"))) From 9e50d4ab90121b52d01501c2a18d13aebb4baa3c Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 17:20:18 -0400 Subject: [PATCH 093/168] matlab-scan.el: (matlab--scan-block-backward-up-until): New matlab.el: (matlab-find-unreachable-code): Delete (matlab-file-basic-font-lock-keywords): No more unreachable code. (matlab-forward-sexp): Remove INCLUDEELSE input. Remove usage from everywhere else. (matlab-beginning-of-enclosing-defun): DELETE. (matlab-beginning-of-defun, matlab-end-of-defun): Use new block scanner for impl. (matlab-calculate-indentation-1): Use new block scanner for finding defun. (matlab-hideshow-*): Deleted - it was never finished. matlab-semantic.el: Update to new matlab-forward-sexp inputs. --- matlab-scan.el | 16 ++++++ matlab.el | 134 ++++++--------------------------------------- semantic-matlab.el | 4 +- 3 files changed, 35 insertions(+), 119 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index d63f3f9..a0d9956 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1058,6 +1058,22 @@ Instead, travel to end as if on keyword." (let ((currentstate '((end "end" 0)))) (matlab--scan-block-backward bounds currentstate))) +(defun matlab--scan-block-backward-up-until (types &optional bounds) + "Call `matlab--scan-block-backward-up' until we find a keyword of TYPES. +Return a keyword node when a matching node is found. +Limit search to within BOUNDS. If keyword not found, return nil." + (when (symbolp types) (setq types (list types))) + (let ((node nil) (done nil) (start (point))) + (while (and (not done) + (or (not node) (not (memq (car node) types)))) + (if (matlab--scan-block-backward-up bounds) + (setq done t) + (setq node (matlab--mk-keyword-node)))) + (if (not done) + node + (goto-char start) + nil))) + ;;; Searching for keywords ;; ;; These utilities will simplify searching for code bits by skipping diff --git a/matlab.el b/matlab.el index ab7eb53..e5d869c 100644 --- a/matlab.el +++ b/matlab.el @@ -725,54 +725,6 @@ when attempting to understand the current context.") ;; We made a change t))) - -(defun matlab-find-unreachable-code (limit) - "Find code that is if'd out with if(0) or if(false), and mark it as a comment. -The if(0) and else/end construct should be highlighted differently. -Argument LIMIT is the maximum distance to search." - (if (and (< (point) limit) - (re-search-forward - "\\<\\(if\\>\\s-*(?\\s-*\\(0\\|false\\)\\s-*)?$\\)" - limit t)) - (let ((b1 (match-beginning 1)) - (e1 (match-end 1)) - (b2 nil) (e2 nil) - (b3 nil) (e3 nil)) - (goto-char b1) - (condition-case nil - (progn - ;; Go forward over the matlab sexp. Include scanning - ;; for ELSE since parts of the ELSE block are not - ;; `commented out'. - (matlab-forward-sexp t) - (forward-word -1) - ;; Is there an ELSE in this block? - (if (looking-at (matlab-block-mid-re)) - (progn - (setq b3 (match-beginning 0) - e3 (match-end 0)) - ;; Now find the REAL end. - (matlab-forward-sexp) - (forward-word -1))) - ;; End of block stuff - (if (looking-at (matlab-block-end-re)) - (progn - (setq b2 (match-beginning 0) - e2 (match-end 0)) - ;; make sure something exists... - (if (not b3) (setq b3 b2 e3 e2))) - (error "Eh?")) - ;; Ok, build up some match data. - (set-match-data - (list b1 e2 ;the real deal. - b1 e1 ;if (0) - b2 e2 ;end - b3 e3 ;else (if applicable.) - b1 e3)) ;body commented out. - t) - (error nil))))) - - ;;; Font Lock MLINT data highlighting (defun matlab-font-lock-nested-function-keyword-match (limit) @@ -941,14 +893,6 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) "\\)\\s-*[(,;%\n]") '(1 font-lock-keyword-face)) - ;; How about unreachable code? MUST BE AFTER KEYWORDS in order to - ;; get double-highlighting. - '(matlab-find-unreachable-code - (1 'underline prepend) ;if part - (2 'underline prepend) ;end part - (3 'underline prepend) ;else part (if applicable) - (4 font-lock-comment-face prepend) ;commented out part. - ) ;; Highlight cross function variables '(matlab-font-lock-cross-function-variables-match (1 matlab-cross-function-variable-face prepend)) @@ -1034,7 +978,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." '("^\\s-*\\(properties\\|events\\|arguments\\)\\s-*[(,;%\n]" ("^\\s-*\\(\\sw+\\)\\>" ;; This part matches the variable ;; extend region to match in - (save-excursion (matlab-forward-sexp nil t) (beginning-of-line) (point)) + (save-excursion (matlab-forward-sexp t) (beginning-of-line) (point)) nil (1 font-lock-variable-name-face t)) ) @@ -1631,9 +1575,8 @@ This assumes that expressions do not cross \"function\" at the left margin." (goto-char p) returnme)) -(defun matlab-forward-sexp (&optional includeelse autostart parentblock) +(defun matlab-forward-sexp (&optional autostart parentblock) "Go forward one balanced set of MATLAB expressions. -Optional argument INCLUDEELSE will stop on ELSE if it matches the starting IF. If AUTOSTART is non-nil, assume we are already inside a block, and navigate forward until we exit that block. PARENTBLOCK is used when recursing to validate block starts as being in @@ -1859,39 +1802,26 @@ blocks that change the syntax of their contents, such as: (interactive) (indent-region (point) (save-excursion (matlab-forward-sexp) (point)) nil)) -(defun matlab-beginning-of-enclosing-defun () - "Move cursor to beginning of enclosing function. -If `matlab-functions-have-end', skip over functions with end." - (catch 'done - (let ((start (point)) - (beg nil)) - (while (re-search-backward matlab-defun-regex nil t) - (setq beg (point)) - (condition-case nil - (progn - (matlab-forward-sexp) - (if (> (point) start) (throw 'done beg))) - (error (throw 'done beg))) - (goto-char beg))) - nil)) - (defun matlab-beginning-of-defun () "Go to the beginning of the current function." (interactive) - (if matlab-functions-have-end - (goto-char (or (matlab-beginning-of-enclosing-defun) (point-min))) - (or (re-search-backward matlab-defun-regex nil t) - (goto-char (point-min))))) + (if (eq (matlab-on-keyword-p) 'end) + (progn + ;; If we are ON an end an it matches a decl, + ;; that is probably what was indented. + (matlab--scan-block-backward) + (if (eq (matlab-on-keyword-p) 'decl) + nil ; done + (matlab--scan-block-backward-up-until 'decl))) + ;; else, just do a scan. + (matlab--scan-block-backward-up-until 'decl))) (defun matlab-end-of-defun () "Go to the end of the current function." (interactive) - (or (progn - (if (looking-at matlab-defun-regex) (goto-char (match-end 0))) - (if (re-search-forward matlab-defun-regex nil t) - (progn (forward-line -1) - t))) - (goto-char (point-max)))) + (when (not (eq (matlab-on-keyword-p) 'decl)) + (matlab--scan-block-backward-up-until 'decl)) + (matlab--scan-block-forward)) (defun matlab-current-defun () "Return the name of the current function." @@ -2017,7 +1947,7 @@ Optional EOL indicates a virtual end of line." (setq v (1+ v)) (let ((p (point))) (forward-word -1) - (if (matlab--scan-block-forward bound) ;;(matlab-forward-sexp) + (if (matlab--scan-block-forward bound) (goto-char p) (setq v (1- v)))))) v)))) @@ -2250,7 +2180,7 @@ LVL2 is a level 2 scan context with info from previous lines." (save-excursion (beginning-of-line) ;; TODO - maybe replace this? Not usually used. - (matlab-beginning-of-enclosing-defun))) + (matlab--scan-block-backward-up-until 'decl))) (setq ci (+ ci matlab-indent-level)) ;; If no intrinsic indentation, do not change from ci. ) @@ -3305,36 +3235,6 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" nil (list here-beg here-end there-beg there-end mismatch) )))))) - -;;; M Block Folding with hideshow ============================================= - -(defun matlab-hideshow-forward-sexp-func (arg) - "Move forward one sexp for hideshow. -Argument ARG specifies the number of blocks to move forward." - (beginning-of-line) - (matlab-forward-sexp arg) - ) - -(defun matlab-hideshow-adjust-beg-func (arg) - "Adjust the beginning of a hideshow block. -Argument ARG to make it happy." - (end-of-line) - (point) - ) - -;; Use this to enable hideshow in MATLAB. -;; It has not been tested by me enough. - -;; REMOVE PUSHNEW FROM THIS LINE -;;(pushnew (list 'matlab-mode -;; (matlab-block-beg-pre) -;; (matlab-block-end-pre) -;; "%" -;; 'matlab-hideshow-forward-sexp-func -;; 'matlab-hideshow-adjust-beg-func -;; ) -;; hs-special-modes-alist :test 'equal) - ;;; M Code verification & Auto-fix ============================================ diff --git a/semantic-matlab.el b/semantic-matlab.el index 0cb5565..346bef7 100644 --- a/semantic-matlab.el +++ b/semantic-matlab.el @@ -206,7 +206,7 @@ Return argument is: (while (re-search-forward semantic-matlab-match-methods-block-re nil end) (save-excursion ;; find end of properties block (goto-char (match-beginning 0)) - (matlab-forward-sexp nil nil) + (matlab-forward-sexp nil) (setq tmpend (point))) (setq attrs (semantic-matlab-parse-attributes-and-move)) @@ -239,7 +239,7 @@ Return argument is: (setq attrs (semantic-matlab-parse-attributes-and-move)) (save-excursion ;; find end of properties block - (matlab-forward-sexp nil t) + (matlab-forward-sexp t) (beginning-of-line) (setq tmpend (point))) From c802dbd8be261ec2388a66ac14768e7516f80615 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 17:51:52 -0400 Subject: [PATCH 094/168] matlab-scan.el: Delete unneeded comments. matlab.el: (matlab-find-prev-line): Replace some code w/ newer apis. (matlab-ltype-comm-noblock): DELETE (matlab-ltype-comm): Fix doc. (matlab-lattr-comm): Delete, it was the same as ltype-comm. (matlab-ltype-comm-ignore, matlab-ltype-help-comm) (matlab-debug-block-comm, matlab-ltype-function-definition) (matlab-ltype-code): DELETE (matlab-calculate-indentation-1): Replace middle block origin check w/ new API calls. Other changes: Replace old fcns with new API calls. Delete unused comments. --- matlab-scan.el | 4 --- matlab.el | 96 +++++++++++++------------------------------------- 2 files changed, 25 insertions(+), 75 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index a0d9956..88fc880 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -499,10 +499,6 @@ The returned LVL2 structure will fill out to be a chain of all previous LVL2 outputs up to a context break. The chains will be summarized in slots in the returned list for quick access." (when (not lvl1) (setq lvl1 (matlab-compute-line-context-lvl-1))) - ;; matlab-ltype-help-comm - ;; block start - ;; block end - ;; change in # blocks on this line. (save-excursion (let ((prev-lvl1 t) diff --git a/matlab.el b/matlab.el index e5d869c..16fd85b 100644 --- a/matlab.el +++ b/matlab.el @@ -1340,9 +1340,12 @@ Return t on success, nil if we couldn't navigate backwards." (if (= -1 (forward-line -1)) nil ;; Now scan backwards iteratively (catch 'moose - (while (or (matlab-ltype-empty) (matlab-ltype-comm-ignore)) - (when (= -1 (forward-line -1)) - (throw 'moose nil))) + (let ((lvl1 (matlab-compute-line-context 1))) + (while (or (matlab-line-empty-p lvl1) + (matlab-line-comment-ignore-p lvl1) + ) + (when (= -1 (forward-line -1)) + (throw 'moose nil)))) t)))) (defun matlab-prev-line () @@ -1845,53 +1848,18 @@ Travells a cross continuations" (matlab-scan-end-of-command)) -;;; Line types, attributes, and string/comment context ================================================= +;;; Line types, attributes, and string/comment context ==================================== (defun matlab-ltype-empty () ; blank line "Return t if current line is empty." (matlab-line-empty-p (matlab-compute-line-context 1))) -(defun matlab-ltype-comm-noblock () - "Return t if the current line is a MATLAB single-line comment. -Returns nil for Cell start %% and block comments %{, %}. -Used in `matlab-ltype-comm', but specialized for not cell start." - (matlab-line-regular-comment-p (matlab-compute-line-context 1))) - (defun matlab-ltype-comm () ; comment line - "Return t if current line is a MATLAB comment line. -Return the symbol 'cellstart if it is a double %%. -Return the symbol 'blockcomm if we are in a block comment." + "Return t if current line is a MATLAB comment line." ;; Looking through uses, no one uses the extra type of output ;; this provides, so replace with very simple call. (matlab-line-comment-p (matlab-compute-line-context 1))) -(defun matlab-ltype-comm-ignore () ; comment out a region line - "Return t if current line is a MATLAB comment region line." - (matlab-line-comment-ignore-p (matlab-compute-line-context 1))) - -(defun matlab-ltype-help-comm () - "Return position of function decl if the point in a MATLAB help comment." - (save-excursion - (if (not (matlab-ltype-comm-noblock)) - nil - (save-restriction - (widen) - (while (and (matlab-ltype-comm-noblock) (not (bobp)) - (matlab-prev-line)) - (beginning-of-line)) - (when (matlab-ltype-function-definition) - (point)))))) - -;; (global-set-key [f6] 'matlab-debug-block-comm) -(defun matlab-debug-block-comm () - "Test block comment detector since font-lock won't let us debug." - (interactive) - (let ((pos (matlab-block-comment-bounds t))) - (if pos - (pulse-momentary-highlight-region (car pos) (cdr pos)) - (message "No block comment.")))) - - (defun matlab-ltype-continued-comm () "Return column of previous line's comment start, or nil." (save-excursion @@ -1905,21 +1873,6 @@ Return the symbol 'blockcomm if we are in a block comment." (forward-line -1) (matlab-line-end-comment-column (matlab-compute-line-context 1)))))) -(defun matlab-ltype-function-definition () - "Return t if the current line is a function definition." - (save-excursion - (beginning-of-line) - (looking-at matlab-defun-regex))) - -(defun matlab-ltype-code () ; line of code - "Return t if current line is a MATLAB code line." - (let ((lvl (matlab-compute-line-context 1))) - (not (or (matlab-line-comment-p lvl) (matlab-line-empty-p lvl))))) - -(defun matlab-lattr-comm () ; line has comment - "Return t if current line contain a comment." - (matlab-line-comment-p (matlab-compute-line-context 1))) - (defun matlab-lattr-block-open (&optional eol) "Return a number representing the number of unterminated block constructs. This is any block, such as if or for, that doesn't have an END on this line. @@ -2005,6 +1958,7 @@ If there isn't one, then return nil, point otherwise." (point))))) ;;; Indent functions ========================================================== +;; (defun matlab-indent-region (start end &optional column noprogress) "Indent the region between START And END for MATLAB mode. @@ -2210,15 +2164,14 @@ LVL2 is a level 2 scan context with info from previous lines." (list 'blockend ci)) ;; ELSE/CATCH keywords ((matlab-line-block-middle-p lvl1) - (let ((m (match-string 1))) - (list 'blockmid - (condition-case nil - (save-excursion - (beginning-of-line) - (matlab-backward-sexp t) - (if (matlab-ltype-function-definition) (error "")) - (current-column)) - (error (error "Unmatched %s" m)))))) + (list 'blockmid + (save-excursion + (back-to-indentation) + (if (matlab--scan-block-backward-up) + (error "Missing start block") + (if (not (eq (matlab-on-keyword-p) 'ctrl)) + (error "Does not match opening block type")) + (current-column))))) ;; CASE/OTHERWISE keywords ((matlab-line-block-case-p lvl1) (list 'blockendless @@ -2518,7 +2471,7 @@ Has effect of `matlab-return' with (not matlab-indent-before-return)." ((matlab-ltype-comm) (matlab-set-comm-fill-prefix) (newline) (insert fill-prefix) (matlab-reset-fill-prefix) (matlab-indent-line)) - ((matlab-lattr-comm) + ((matlab-ltype-comm) (newline) (indent-to comment-column) (insert matlab-comment-on-line-s)) (t @@ -2603,14 +2556,14 @@ Argument ARG specifies how many %s to insert." (call-interactively #'comment-or-uncomment-region)) ((matlab-ltype-empty) ; empty line (matlab-comm-from-prev) - (if (matlab-lattr-comm) + (if (matlab-ltype-comm) (skip-chars-forward " \t%") (insert matlab-comment-line-s) (matlab-indent-line))) ((matlab-ltype-comm) ; comment line (matlab-comm-from-prev) (skip-chars-forward " \t%")) - ((matlab-lattr-comm) ; code line w/ comment + ((matlab-ltype-comm) ; code line w/ comment (beginning-of-line) (re-search-forward "[^%]\\(%\\)[ \t]") (goto-char (match-beginning 1)) @@ -2685,7 +2638,7 @@ Argument BEG and END indicate the region to uncomment." (defun matlab-set-comm-fill-prefix () "Set the `fill-prefix' for the current (comment) line." (interactive) - (if (matlab-lattr-comm) + (if (matlab-ltype-comm) (setq fill-prefix (save-excursion (beginning-of-line) @@ -2726,7 +2679,7 @@ not be broken. This function will ONLY work on code." (orig (point))) (or ;; Next, if we have a trailing comment, use that. - (progn (setq pos (or (matlab-line-comment-p lvl1) ;;(matlab-lattr-comm) + (progn (setq pos (or (matlab-line-comment-p lvl1) ;;(matlab-ltype-comm) (matlab-point-at-bol))) (goto-char pos) (if (and (> (current-column) (- fill-column matlab-fill-fudge)) @@ -3020,7 +2973,8 @@ ARG is passed to `fill-paragraph' and will justify the text." ;; a buffer. (narrow-to-region start (min (point-max) (+ end 1))) (fill-paragraph arg)))) - ((matlab-ltype-code) + ((let ((lvl (matlab-compute-line-context 1))) + (not (or (matlab-line-comment-p lvl) (matlab-line-empty-p lvl)))) ;; Ok, lets get the outer bounds of this command, then ;; completely refill it using the smart line breaking code. (save-restriction @@ -3590,7 +3544,7 @@ desired. Optional argument FAST is not used." (if (matlab-line-ellipsis-p lvl1) (setq msg (concat msg " w/cont"))) - (if (matlab-lattr-comm) + (if (matlab-ltype-comm) (setq msg (concat msg " w/comm"))) (message "%s" msg)))) From d24178e015b17c09d3430f5d8434bd0e6bd68009 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 19:53:01 -0400 Subject: [PATCH 095/168] matlab-scan.el: (mlf-paren-inner-point): New var. Renumbered all the other vars. (matlab-compute-line-context-lvl-1): Add paren inner point into list. (matlab-line-close-paren-inner-point): New matlab.el: (matlab-calculate-indentation-1): Update blockendless to use new apis Update plain code continuations to use new apis when inside parens. --- matlab-scan.el | 20 +++-- matlab.el | 190 ++++++++++++++++++++---------------------- tests/continuations.m | 27 ++++++ 3 files changed, 131 insertions(+), 106 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 88fc880..2da6633 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -165,11 +165,12 @@ return the cache if it finds it." (defconst mlf-paren-depth 5) (defconst mlf-paren-inner-char 6) (defconst mlf-paren-inner-col 7) -(defconst mlf-paren-outer-char 8) -(defconst mlf-paren-outer-point 9) -(defconst mlf-paren-delta 10) -(defconst mlf-end-comment-type 11) -(defconst mlf-end-comment-pt 12) +(defconst mlf-paren-inner-point 8) +(defconst mlf-paren-outer-char 9) +(defconst mlf-paren-outer-point 10) +(defconst mlf-paren-delta 11) +(defconst mlf-end-comment-type 12) +(defconst mlf-end-comment-pt 13) (defun matlab-compute-line-context-lvl-1 () "Compute and return the level1 context for the current line of MATLAB code. @@ -189,6 +190,7 @@ in a single call using fastest methods." (paren-depth (nth 0 pps)) (paren-inner-char nil) (paren-inner-col nil) + (paren-inner-point nil) (paren-outer-char nil) (paren-outer-point nil) (paren-delta (- (car pps) (car ppsend))) @@ -207,6 +209,7 @@ in a single call using fastest methods." (goto-char (car (last (nth 9 pps)))) (setq paren-inner-char (char-after (point)) paren-inner-col (current-column) + paren-inner-point (point) paren-outer-point (car (nth 9 pps)) paren-outer-char (char-after paren-outer-point) ))) @@ -290,7 +293,8 @@ in a single call using fastest methods." )) (list ltype stype pt indent start paren-depth - paren-inner-char paren-inner-col paren-outer-char paren-outer-point paren-delta + paren-inner-char paren-inner-col paren-inner-point + paren-outer-char paren-outer-point paren-delta ec-type ec-col ;;cont-from-prev ) @@ -452,6 +456,10 @@ Return nil for empty and comment only lines." "Return the paren column for the prenthetical expression LVL1 is in." (nth mlf-paren-inner-col lvl1)) +(defsubst matlab-line-close-paren-inner-point (lvl1) + "Return the paren column for the prenthetical expression LVL1 is in." + (nth mlf-paren-inner-point lvl1)) + (defsubst matlab-line-close-paren-outer-char (lvl1) "The paren character for the outermost prenthetical expression LVL1 is in." (nth mlf-paren-outer-char lvl1)) diff --git a/matlab.el b/matlab.el index 16fd85b..5309e7f 100644 --- a/matlab.el +++ b/matlab.el @@ -2175,16 +2175,16 @@ LVL2 is a level 2 scan context with info from previous lines." ;; CASE/OTHERWISE keywords ((matlab-line-block-case-p lvl1) (list 'blockendless - (condition-case nil - (save-excursion - (beginning-of-line) - (matlab-backward-sexp t) - (if (not (looking-at "switch\\>")) (error "")) + (save-excursion + (back-to-indentation) + (if (matlab--scan-block-backward-up) + (error "Missing switch") + (if (not (eq (matlab-on-keyword-p) 'ctrl)) + (error "Wrong type of start block") (+ (current-column) (if (listp matlab-case-indent-level) (car matlab-case-indent-level) - matlab-case-indent-level))) - (error (error "Unmatched case/otherwise part"))))) + matlab-case-indent-level))))))) ;; End of a MATRIX ((matlab-line-close-paren-p lvl1) (list 'array-end (let* ((fc (matlab-line-close-paren-inner-char lvl1)) @@ -2203,104 +2203,93 @@ LVL2 is a level 2 scan context with info from previous lines." ;; Code lines ((and (not (matlab-line-close-paren-outer-point lvl1)) (not (matlab-scan-previous-line-ellipsis-p))) - - ;; Old check for base code line was using - ;; 'matlab-beginning-of-command' which does a lot of work, like - ;; checking for block comments and such. We already stripped - ;; out most options in that cmd and know we are on a code line, - ;; so only check if the previous line is a continuation line. - ;; Old code: - ;;(save-excursion - ;; (beginning-of-line) - ;; (back-to-indentation) - ;; (= (point) (progn (matlab-beginning-of-command) (point)))) - - ;; This means we are at the beginning of a command structure. - ;; Always match up against the previous line. + ;; Code always matches up against the previous line. (list 'code ci)) + ;; Lines continued from previous statements. (t - (list (if (matlab-line-empty-p lvl1) 'empty - (if (matlab-line-close-paren-outer-point lvl1) 'array-cont 'code)) - ;; Record beginning of the command - (let* ((ci-local ci) ;; we prob want to change this due to continuation. - (boc (save-excursion - (matlab-beginning-of-command) - (setq ci-local (current-indentation)) - (point)))) + (let* (;; TODO = Why does this code need to navigate + ;; through this stuff? next-line version + ;; should do this. + (boc-lvl1 (save-excursion + (matlab-scan-beginning-of-command) + (matlab-compute-line-context 1))) + (ci-boc (matlab-line-indentation boc-lvl1)) + (boc (matlab-line-point boc-lvl1)) + + ;; Scratch vars for paren stuff + (parencol (matlab-line-close-paren-inner-col lvl1)) + (parenchar (matlab-line-close-paren-inner-char lvl1)) + (parenpt (matlab-line-close-paren-inner-point lvl1)) + + ;; What shall we use to describe this for debugging? + (indent-type (cond ((matlab-line-empty-p lvl1) 'empty) + ((and parenchar (= parenchar ?\()) 'function-call-cont) + ((matlab-line-close-paren-outer-point lvl1) 'array-cont) + (t 'code-cont))) + + (found-column nil) + ) + + (list indent-type (condition-case nil (save-excursion - (beginning-of-line) - (matlab-up-list -1) - (if (> boc (point)) (error nil)) - ;; Ok, it MIGHT be that we are in a program - ;; statement, and this particular command is an HG - ;; statement that would look better if the - ;; following lines lined up AFTER the first - ;; argument. Lets look. - (let ((parendepth (current-column))) - (cond ((and (= (following-char) ?\( ) - (save-excursion - (matlab-navigation-syntax - (forward-word -1) - (looking-at - matlab-indent-past-arg1-functions))) - (let ((start-paren (point))) - (while - (and - (re-search-forward - "," (matlab-point-at-eol) t) - (save-excursion - (matlab-up-list -1) - (> (point) start-paren)))) - (if (and - (= (preceding-char) ?,) - ;; Don't bother if we hit the EOL. - (not (looking-at + (goto-char parenpt) ;; this errs and goes to = alignment. TODO - make w/o errors - "\\s-*\\(\\.\\.\\.\\|$\\|)\\)"))) - t - (move-to-column parendepth) - nil))) - (skip-chars-forward " \t") - (if (> (- (current-column) parendepth) - matlab-arg1-max-indent-length) - (+ parendepth matlab-arg1-max-indent-length) - (current-column))) - (t - (let* ((fc (following-char)) - (mi (assoc fc matlab-maximum-indents)) - (max (if mi - (if (listp (cdr mi)) - (car (cdr mi)) (cdr mi)) - nil)) - (ind (if mi - (if (listp (cdr mi)) - (cdr (cdr mi)) (cdr mi)) - nil))) - (forward-char 1) - (skip-chars-forward " \t") - ;; If we are at the end of a line and - ;; this open paren is there, then we - ;; DON'T want to indent to it. Use the - ;; standard indent. - (if (or (not matlab-align-to-paren) - (looking-at "\\.\\.\\.\\|$")) - ;; This could happen in another set - ;; of matrices. Find a current - ;; indentation based on the - ;; previous line. - (let ((cci (current-indentation))) - (+ cci matlab-continuation-indent-level)) - ;; TODO - this disables indentation MAXs - ;; if we really want to be rid of this - ;; we can dump a bunch of logic above too. - ;; apply the maximum limits. - ;;(if (and ind (> (- (current-column) ci-local) max)) - ;; (+ ci-local ind) - ;; (current-column)) - (current-column) - )))))) + (progn + (cond + ((and + ;; Special case for FUNCTIONs that indent past arg1 + (eq indent-type 'function-call-cont) + ;; This checks for our special set of functions. + (save-excursion + (goto-char parenpt) + (forward-symbol -1) + (looking-at + matlab-indent-past-arg1-functions)) + ;; We are in a fcn call, AND the fcn wants to + ;; indent past the first argument. Only do so + ;; if first arg is a SIMPLE EXPR. + (matlab-navigation-syntax + (looking-at "(\\s-*\\w+\\s-*,") + (setq found-column (match-end 0))) + (save-excursion + (goto-char found-column) ; move to comma + ;; Don't bother if we hit the EOL. + (not (looking-at "\\s-*\\(\\.\\.\\.\\|$\\|)\\)")))) + ;; We are in the right kind of place. Lets + ;; start indenting + (goto-char found-column) + (skip-chars-forward " \t") + (if (> (- (current-column) parencol) + matlab-arg1-max-indent-length) + (+ parencol matlab-arg1-max-indent-length) + (current-column))) + + ;; any other case LINE UP WITH THE PAREN FOUND + (t + (let* ((mi (assoc parenchar matlab-maximum-indents)) + (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) nil)) + (ind (if mi (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)) nil))) + (goto-char parenpt) + (forward-char 1) + (skip-chars-forward " \t") + ;; If we are at the end of a line and this + ;; open paren is there, then we DON'T want + ;; to indent to it. Use the standard + ;; indent. + (if (or (not matlab-align-to-paren) + (looking-at "\\.\\.\\.\\|$")) + (+ ci-boc matlab-continuation-indent-level) + (current-column) + ;; TODO - this disables indentation MAXs + ;; if we really want to be rid of this + ;; we can dump a bunch of logic above too. + ;; apply the maximum limits. + ;;(if (and ind (> (- (current-column) ci-boc) max)) + ;; (+ ci-boc ind) + ;; (current-column)) + )))))) (error ;; Line up to an equals sign. (save-excursion @@ -3540,6 +3529,7 @@ desired. Optional argument FAST is not used." "Line Syntax: " lvl1msg " | Preferred Indents: This: " (int-to-string (nth 1 indent)) " Next: " (int-to-string nexti) + " Indent Style: " (symbol-name (car indent)) )) (if (matlab-line-ellipsis-p lvl1) @@ -3567,7 +3557,7 @@ desired. Optional argument FAST is not used." ;; LocalWords: Unstarted includeelse autostart lattr zerop cellstart ;; LocalWords: linebounds bol commtype startmove nomove charvector sregex ;; LocalWords: insregex laststart bolp calc ci sem DEPTHNUMBER blockstart -;; LocalWords: blockmid blockendless blockend unstarted listp boc parendepth +;; LocalWords: blockmid blockendless blockend unstarted listp boc parencol ;; LocalWords: cci startpnt hc rc nosemi emacsen afterd befored okpos startlst ;; LocalWords: endlst ellipsify noreturn hs tc matchers hideshow func PUSHNEW ;; LocalWords: pushnew bn nondirectory un msgpos nexti diff --git a/tests/continuations.m b/tests/continuations.m index fb37e5f..a52bd4f 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -62,6 +62,33 @@ function continuations(a,b) %!!0 % !!4 to undo continuation. + % Indent past 1st arg for special functions + set(myhandle, 'Prop1', value, ... %!!4 + 'Prop2', value, ... %!!18 + 'Prop3', value); %!!18 + + % Indent past = sign for assignments. + A = 1 + ... % !!4 + 2; % !!8 + + medvar = 1 + ... % !!4 + 2; % !!13 + + alongvariablename = 1 +... % !!4 + 2; % !!8 + + + fancyfunctionname(arg1, ... %!!4 + innerfcn(arg2, ... %!!22 + arg3), ... %!!31 + { cell1; %!!22 + cell2; %!!24 + [ 1 2 ; %!!24 + 3 4 ] ; %!!26 + cell 4 },... %!!24 + arg5); %!!22 + + ... % Continuation by itself just before an end. end %!!0 From 0670a2738521a86659a2c7cb787564fb537ad6d0 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 20:14:01 -0400 Subject: [PATCH 096/168] matlab.el: (matlab-calculate-indentation-1): Flatten out the indentation section on continuations. Break each style out into a different section of a cond statement so they are findable and various states aren't lost in a cascade of if statements. --- matlab.el | 178 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 91 insertions(+), 87 deletions(-) diff --git a/matlab.el b/matlab.el index 5309e7f..f384a9c 100644 --- a/matlab.el +++ b/matlab.el @@ -2125,6 +2125,7 @@ LVL2 is a level 2 scan context with info from previous lines." (list 'comment tmp)) (t (list 'comment (+ ci matlab-comment-anti-indent)))))) + ;; FUNCTION DEFINITION ((matlab-line-declaration-p lvl1) (if matlab-functions-have-end @@ -2185,7 +2186,7 @@ LVL2 is a level 2 scan context with info from previous lines." (if (listp matlab-case-indent-level) (car matlab-case-indent-level) matlab-case-indent-level))))))) - ;; End of a MATRIX + ;; END of a MATRIX ((matlab-line-close-paren-p lvl1) (list 'array-end (let* ((fc (matlab-line-close-paren-inner-char lvl1)) (pc (matlab-line-close-paren-inner-col lvl1)) @@ -2200,18 +2201,15 @@ LVL2 is a level 2 scan context with info from previous lines." (if (and ind (> (- pc ci) max)) (1- ind) ; decor pc)))) - ;; Code lines + ;; CODE LINES ((and (not (matlab-line-close-paren-outer-point lvl1)) (not (matlab-scan-previous-line-ellipsis-p))) ;; Code always matches up against the previous line. (list 'code ci)) - ;; Lines continued from previous statements. + ;; CONTINUATION : A group of cases for continuation (t - (let* (;; TODO = Why does this code need to navigate - ;; through this stuff? next-line version - ;; should do this. - (boc-lvl1 (save-excursion + (let* ((boc-lvl1 (save-excursion (matlab-scan-beginning-of-command) (matlab-compute-line-context 1))) (ci-boc (matlab-line-indentation boc-lvl1)) @@ -2232,88 +2230,94 @@ LVL2 is a level 2 scan context with info from previous lines." ) (list indent-type - (condition-case nil + (save-excursion + (cond + ((and + ;; CONTINUATION with FUNCTIONs that indent past arg1 + (eq indent-type 'function-call-cont) + ;; This checks for our special set of functions. + (save-excursion + (goto-char parenpt) + (forward-symbol -1) + (looking-at + matlab-indent-past-arg1-functions)) + ;; We are in a fcn call, AND the fcn wants to + ;; indent past the first argument. Only do so + ;; if first arg is a SIMPLE EXPR. + (matlab-navigation-syntax + (goto-char parenpt) + (looking-at "(\\s-*\\w+\\s-*,") + (setq found-column (match-end 0))) + (save-excursion + (goto-char found-column) ; move to comma + ;; Don't bother if we hit the EOL. + (not (looking-at "\\s-*\\(\\.\\.\\.\\|$\\|)\\)")))) + ;; We are in the right kind of place. Lets + ;; start indenting + (goto-char found-column) + (skip-chars-forward " \t") + (if (> (- (current-column) parencol) + matlab-arg1-max-indent-length) + (+ parencol matlab-arg1-max-indent-length) + (current-column))) + + ;; CONTINUATION with PARENS + (parenchar ;; a str if in parens + (let* ((mi (assoc parenchar matlab-maximum-indents)) + (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) nil)) + (ind (if mi (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)) nil))) + (goto-char parenpt) + (forward-char 1) + (skip-chars-forward " \t") + ;; If we are at the end of a line and this + ;; open paren is there, then we DON'T want + ;; to indent to it. Use the standard + ;; indent. + (if (or (not matlab-align-to-paren) + (looking-at "\\.\\.\\.\\|$")) + (+ ci-boc matlab-continuation-indent-level) + (current-column) + ;; TODO - this disables indentation MAXs + ;; if we really want to be rid of this + ;; we can dump a bunch of logic above too. + ;; apply the maximum limits. + ;;(if (and ind (> (- (current-column) ci-boc) max)) + ;; (+ ci-boc ind) + ;; (current-column)) + ))) + + ;; CONTINUATION with EQUALS + ((save-excursion + (goto-char boc) + (while (and (re-search-forward "=" (matlab-point-at-eol) t) + (matlab-cursor-in-string-or-comment))) + (when (= (preceding-char) ?=) + (skip-chars-forward " \t") + (setq found-column (point))) + ) (save-excursion - (goto-char parenpt) ;; this errs and goes to = alignment. TODO - make w/o errors + (goto-char found-column) + (let ((cc (current-column)) + (mi (assoc ?= matlab-maximum-indents))) + + (if (looking-at "\\.\\.\\.\\|$") + ;; In this case, the user obviously wants the + ;; indentation to be somewhere else. + (+ ci (cdr (cdr mi))) + ;; If the indent delta is greater than the max, + ;; use the max + current + (if (and mi (> (- cc ci) (if (listp (cdr mi)) + (car (cdr mi)) + (cdr mi)))) + (setq cc (+ ci (if (listp (cdr mi)) + (cdr (cdr mi)) + (cdr mi)))) + cc))))) - (progn - (cond - ((and - ;; Special case for FUNCTIONs that indent past arg1 - (eq indent-type 'function-call-cont) - ;; This checks for our special set of functions. - (save-excursion - (goto-char parenpt) - (forward-symbol -1) - (looking-at - matlab-indent-past-arg1-functions)) - ;; We are in a fcn call, AND the fcn wants to - ;; indent past the first argument. Only do so - ;; if first arg is a SIMPLE EXPR. - (matlab-navigation-syntax - (looking-at "(\\s-*\\w+\\s-*,") - (setq found-column (match-end 0))) - (save-excursion - (goto-char found-column) ; move to comma - ;; Don't bother if we hit the EOL. - (not (looking-at "\\s-*\\(\\.\\.\\.\\|$\\|)\\)")))) - ;; We are in the right kind of place. Lets - ;; start indenting - (goto-char found-column) - (skip-chars-forward " \t") - (if (> (- (current-column) parencol) - matlab-arg1-max-indent-length) - (+ parencol matlab-arg1-max-indent-length) - (current-column))) - - ;; any other case LINE UP WITH THE PAREN FOUND - (t - (let* ((mi (assoc parenchar matlab-maximum-indents)) - (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) nil)) - (ind (if mi (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)) nil))) - (goto-char parenpt) - (forward-char 1) - (skip-chars-forward " \t") - ;; If we are at the end of a line and this - ;; open paren is there, then we DON'T want - ;; to indent to it. Use the standard - ;; indent. - (if (or (not matlab-align-to-paren) - (looking-at "\\.\\.\\.\\|$")) - (+ ci-boc matlab-continuation-indent-level) - (current-column) - ;; TODO - this disables indentation MAXs - ;; if we really want to be rid of this - ;; we can dump a bunch of logic above too. - ;; apply the maximum limits. - ;;(if (and ind (> (- (current-column) ci-boc) max)) - ;; (+ ci-boc ind) - ;; (current-column)) - )))))) - (error - ;; Line up to an equals sign. - (save-excursion - (goto-char boc) - (while (and (re-search-forward "=" (matlab-point-at-eol) t) - (matlab-cursor-in-string-or-comment))) - (if (/= (preceding-char) ?=) - (+ ci matlab-continuation-indent-level) - (skip-chars-forward " \t") - (let ((cc (current-column)) - (mi (assoc ?= matlab-maximum-indents))) - (if (looking-at "\\.\\.\\.\\|$") - ;; In this case, the user obviously wants the - ;; indentation to be somewhere else. - (+ ci (cdr (cdr mi))) - ;; If the indent delta is greater than the max, - ;; use the max + current - (if (and mi (> (- cc ci) (if (listp (cdr mi)) - (car (cdr mi)) - (cdr mi)))) - (setq cc (+ ci (if (listp (cdr mi)) - (cdr (cdr mi)) - (cdr mi))))) - cc))))))))) + ;; CONTINUATION with nothing special about it. + (t + (+ ci matlab-continuation-indent-level)) + ))))) ))) (defun matlab-next-line-indentation (lvl1) From 3afd70f47b167c2056f9f56ba629f43eab5132d7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 20:19:58 -0400 Subject: [PATCH 097/168] matlab-complete.el: (matlab-uniquify-list): Move to be before first use. Add check for cl-remove-duplicates, and use it if available. --- matlab-complete.el | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/matlab-complete.el b/matlab-complete.el index a26c6ba..8dbf381 100644 --- a/matlab-complete.el +++ b/matlab-complete.el @@ -30,9 +30,22 @@ ;; and this can be loaded optionally. ;;; Code: +(require 'cl-macs) (require 'matlab) (require 'matlab-shell) +(defun matlab-uniquify-list (lst) + "Return a list that is a subset of LST where all elements are unique." + (if (fboundp 'cl-remove-duplicates) + (cl-remove-duplicates lst :test 'string= :from-end t) + ;; Else, do it by hand. + (let ((nlst nil)) + (while lst + (if (and (car lst) (not (member (car lst) nlst))) + (setq nlst (cons (car lst) nlst))) + (setq lst (cdr lst))) + (nreverse nlst)))) + ;;; Customizations =========================================================== ;; (defcustom matlab-completion-technique 'complete @@ -209,15 +222,6 @@ line." ;;; Completion Framework =================================================== ;; -(defun matlab-uniquify-list (lst) - "Return a list that is a subset of LST where all elements are unique." - (let ((nlst nil)) - (while lst - (if (and (car lst) (not (member (car lst) nlst))) - (setq nlst (cons (car lst) nlst))) - (setq lst (cdr lst))) - (nreverse nlst))) - (defun matlab-find-recent-variable-list (prefix) "Return a list of most recent variables starting with PREFIX as a string. Reverse searches for the following are done first: From 295592e006c0011515d9d7c1175fd0e951289676 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 20:56:41 -0400 Subject: [PATCH 098/168] matlab.el: (matlab-block-indent-tic-toc-flag) (matlab-block-syntax-re) (matlab-innerblock-syntax-re) (matlab-block-mid-pre) (matlab-block-end-pre-if) (matlab-block-end-pre-no-if) (matlab-block-end-pre) (matlab-endless-blocks) (matlab-block-re) (matlab-block-star-tscan-re) (matlab-block-scan-re) (matlab-block-beg-re) (matlab-block-beg-re) (matlab-block-mid-re) (matlab-block-end-re) (matlab-block-end-no-function-re) (matlab-endless-blocks-re): DELETED (matlab-block-beg-pre-no-if, matlab-block-beg-pre-if): Stop doing stuff with tic-toc indent. (matalb-cline-start-skip): Moved (matlab-valid-blok-start-slow-and-careful) (matlab-cursor-on-valid-block-start) (matlab-previous-line-belongs-to-classdef-p): DELETED (matlab-mode-vf-block-matches-forward) (matlab-mode-vf-block-matches-backward): Use newer api to stop using regexp. --- matlab.el | 296 +++--------------------------------------------------- 1 file changed, 13 insertions(+), 283 deletions(-) diff --git a/matlab.el b/matlab.el index f384a9c..aa8ce34 100644 --- a/matlab.el +++ b/matlab.el @@ -1389,37 +1389,15 @@ asside from that which they declare their content.") (defconst matlab-mcos-regexp (concat "\\|classdef\\|" matlab-mcos-innerblock-regexp) "Keywords which mark the beginning of mcos blocks.") -(defcustom matlab-block-indent-tic-toc-flag nil - "*Non-nil means that tic,toc should indent like a if,end block. -This variable should be set before loading matlab.el" - :group 'matlab - :type 'boolean) - -(defconst matlab-block-syntax-re - (concat "\\(function" matlab-mcos-regexp "\\)\\>") - "Keywords that represent blocks that have custom internal syntax. -Used by `matlab-cursor-on-valid-block-start'.") - -(defconst matlab-innerblock-syntax-re - (concat "\\(" matlab-mcos-innerblock-regexp "\\)\\>") - "Keywords that represent blocks that have custom internal syntax. -Used by `matlab-cursor-on-valid-block-start'.") - (defconst matlab-block-beg-pre-if - (if matlab-block-indent-tic-toc-flag - (concat "function\\|parfor\\|spmd\\|for\\|while\\|if\\|switch\\|try\\|tic" - matlab-mcos-regexp) - (concat "function\\|parfor\\|spmd\\|for\\|while\\|if\\|switch\\|try" - matlab-mcos-regexp)) + (concat "function\\|parfor\\|spmd\\|for\\|while\\|if\\|switch\\|try" + matlab-mcos-regexp) "Keywords which mark the beginning of an indented block. Includes function.") (defconst matlab-block-beg-pre-no-if - (if matlab-block-indent-tic-toc-flag - (concat "parfor\\|for\\|spmd\\|while\\|if\\|switch\\|try\\|tic" - matlab-mcos-regexp) - (concat "parfor\\|for\\|spmd\\|while\\|if\\|switch\\|try" - matlab-mcos-regexp)) + (concat "parfor\\|for\\|spmd\\|while\\|if\\|switch\\|try" + matlab-mcos-regexp) "Keywords which mark the beginning of an indented block. Excludes function.") @@ -1429,76 +1407,6 @@ Excludes function.") matlab-block-beg-pre-if matlab-block-beg-pre-no-if)) -(defconst matlab-block-mid-pre - "elseif\\|else\\|catch" - "Partial regular expression to recognize MATLAB mid-block keywords.") - -(defconst matlab-block-end-pre-if - (if matlab-block-indent-tic-toc-flag - "end\\|function\\|\\(\\sw+\\s-*\\((.*)\\)?\\s-*=\\s-*\\)?toc" - "end\\|function") - "Partial regular expression to recognize MATLAB block-end keywords.") - -(defconst matlab-block-end-pre-no-if - (if matlab-block-indent-tic-toc-flag - "end\\|\\(\\sw+\\s-*\\((.*)\\)?\\s-*=\\s-*\\)?toc" - "end") - "Partial regular expression to recognize MATLAB block-end keywords.") - -(defun matlab-block-end-pre () - "Partial regular expression to recognize MATLAB block-end keywords." - (if matlab-functions-have-end - matlab-block-end-pre-no-if - matlab-block-end-pre-if)) - -(defconst matlab-endless-blocks - "case\\|otherwise" - "Keywords which initialize new blocks, but don't have explicit ends. -Thus, they are endless. A new case or otherwise will end a previous -endless block, and end will end this block, plus any outside normal -blocks.") - -(defun matlab-block-re () - "Regular expression for keywords which begin MATLAB blocks." - (concat "\\(^\\|[;,]\\)\\s-*\\(" - (matlab-block-beg-pre) "\\|" - matlab-block-mid-pre "\\|" - (matlab-block-end-pre) "\\|" - matlab-endless-blocks "\\)\\b")) - -(defun matlab-block-start-scan-re () - "Expression used to scan over matching pairs of begin/ends. -Assume cursor is on the beginning of the upcoming word." - (concat "\\(" - (matlab-block-beg-pre) "\\|" - matlab-block-mid-pre "\\)\\b")) - -(defun matlab-block-scan-re () - "Expression used to scan over matching pairs of begin/ends." - (concat "\\(^\\|[;,]\\)\\s-*\\(" - (matlab-block-beg-pre) "\\|" - (matlab-block-end-pre) "\\)\\b")) - -(defun matlab-block-beg-re () - "Expression used to find the beginning of a block." - (concat "\\(" (matlab-block-beg-pre) "\\)")) - -(defun matlab-block-mid-re () - "Expression used to find block center parts (like else)." - (concat "\\(" matlab-block-mid-pre "\\)")) - -(defun matlab-block-end-re () - "Expression used to end a block. Usually just `end'." - (concat "\\(" (matlab-block-end-pre) "\\)")) - -(defun matlab-block-end-no-function-re () - "Expression representing and end if functions are excluded." - (concat "\\<\\(" matlab-block-end-pre-no-if "\\)\\>")) - -(defun matlab-endless-blocks-re () - "Expression of block starters that do not have associated ends." - (concat "\\(" matlab-endless-blocks "\\)")) - (defun matlab-match-function-re () "Expression to match a function start line. There are no reliable numeric matches in this expression. @@ -1513,10 +1421,6 @@ Know that `match-end' of 0 is the end of the function name." The class name is match 2." "\\(^\\s-*classdef\\b[ \t\n]*\\)\\(\\sw+\\)\\(\\s-*<\\)?") -(defconst matlab-cline-start-skip "[ \t]*%[ \t]*" - "*The regular expression for skipping comment start.") - - ;;; Navigation =============================================================== (defvar matlab-scan-on-screen-only nil @@ -1624,182 +1528,6 @@ a valid context." (setq p (point))) (goto-char p))) -(defvar matlab-valid-block-start-slow-and-careful t - "Be very careful with determining of a block is valid when t. -Set to nil if fast-and-loose is ok.") - -(defun matlab-cursor-on-valid-block-start (&optional known-parent-block) - "Return t if cursor is on a valid block start. -Valid block starts are those that represent a syntax context, like function, -classdef, properties, etc. -KNOWN-PARENT-BLOCK is a string that represents the context cursor is in. -Use this if you know what context you're in." - (save-match-data - (save-restriction - (widen) - (let ((foundblock (and (looking-at (matlab-block-beg-re)) - (match-string-no-properties 1)))) - (cond - ((not foundblock) - ;; Not looking at a valid block - nil) - ((eq (preceding-char) ?.) - ;; Any block preceeded by a '.' is a field in a struct, and not valid. - nil) - ;; Else, are we on a block that has special syntax? - ((not (looking-at matlab-innerblock-syntax-re)) - ;; Not an innerblock syntax that only work withing special blocks - ;; thus automatically true. - t) - - ;; Else, a special block keyword. We need to check the context of this - ;; block to know if this innerblock is valid. - - ;; Cheap check - if functions don't have end, then always invalid - ;; since these context blocks can't exist. - ((not matlab-functions-have-end) - nil) - - ;; Cheap check - is this block keyword not the first word for this command? - ((save-excursion (skip-syntax-backward " ") (not (bolp))) - ;; Not first on line, not valid block. - ;; Technically it COULD be valid, but we need some cheap ways - ;; to skip over some types of syntaxes that look dumb. - nil) - - ;; Cheap check - is this at the beginning of a command line (ignore ... ) - ((not (eq (save-excursion (back-to-indentation) (point)) - (save-excursion (matlab-scan-beginning-of-command) (point)))) - ;; If this statement is not also the first word on this command - ;; then it can't be one of these features. - nil) - - ;; Expensive check - is this block in the right context? - ((or matlab-valid-block-start-slow-and-careful known-parent-block) - - (cond - ((string= foundblock "arguments") - ;; Argument is only valid if it is the FIRST thing in a funtion. - (save-excursion - (if (and (matlab-find-prev-code-line) (looking-at "\\s-*function\\>")) - ;; If the previous code line (ignoring whitespace and comments) - ;; is 'arguments', then that is a valid block. - t - ;; Otherewise, all other argument cases are bad. - nil))) - - ;; Other special blocks are in a class. If not in a class file, fail. - ((not (eq matlab-functions-have-end 'class)) - nil) - - ;; If a known parent block was passed in, then this will be a very - ;; fast check, so do that next. If it is the right thing, then - ;; true! - (known-parent-block - (if (string= known-parent-block "classdef") - t - ;; otherwise not correct. - nil)) - - ;; This test goes back one command. If it is an end, and it is indented just - ;; one level, then we are good b/c we already check we're in a classdef file. - ((save-excursion - (if (not (matlab-find-prev-code-line)) - nil ;; no code, so not in a class. - (back-to-indentation) - (or - ;; Looking at correctly indented end - (and (looking-at "\\") - (eq (current-indentation) matlab-indent-level)) - ;; Looking at the classdef itself - (looking-at "\\") ))) - t) - - ;; This is a medium speed test for classdef stuff. It only navigates backward - ;; one step. If the previous thing also belongs to a class, then we must - ;; be in a class. - ;;((matlab-previous-line-belongs-to-classdef-p) t) - - ;; This is a slow operation - navigating backward to find the current syntactic - ;; block is pretty expensive, but it also always gets it right. - ;; - ;; Since We are in a class, identify if this is in a class context. - ;; ((let ((myblock (if known-parent-block - ;; (cons known-parent-block nil) ;; shortcut if known - ;; (matlab-current-syntactic-block)))) - ;; (string= (car myblock) "classdef")) - ;; ;; We found correct usage of methods, events, etc. - ;; t) - - ;; All else fails - so not valid. - (t nil) - - )) ;; End slow-and-careful - - ;; A cheap version of the expensive check - ((and (not matlab-valid-block-start-slow-and-careful) - (looking-at matlab-innerblock-syntax-re)) - ;; If we are not slow and careful, we just need to return t if we see - ;; one of these keywords since other cases where these weren't 1st on the line - ;; or not in a classdef file are already filtered out. - t) - - ;; If none of the valid cases, must be invalid - (t nil) - ))))) - -(defun matlab-previous-line-belongs-to-classdef-p () - "Return the nature of the line of code before this one. -Ignores comments, etc. -Returns non-nil if that previous thing is unique to a classdef." - (save-excursion - (save-match-data - ;; Move to the syntax in question. - (if (not (matlab-find-prev-code-line)) - nil ;; No code - ;; Lets see what it is. - (back-to-indentation) - ;; Is it an end? Nav backward - (if (looking-at "\\") - (if (not (matlab-backward-sexp t t)) - nil ;; failed to nav - just skip it. - ;; If no errors so far, compute the kind of block. - (if (looking-at matlab-innerblock-syntax-re) - t ;; found something that belongs to a classdef. - nil)) - ;; Not on an end. Is that because we are now on a classdef? - ;; If so, then this is our context, so that's ok. - (if (looking-at "\\") - t ;; Yep, that's a class - nil)))))) - -(defun matlab-current-syntactic-block () - "Return information about the current syntactic block the cursor is in. -Value returned is of the form ( BLOCK-TYPE . PT ) where BLOCK-TYPE is a -string, such as 'function' or 'properties', and PT is the location that -the block starts at. - -This function skips over blocks such as 'switch' and 'if', and only returns -blocks that change the syntax of their contents, such as: - function, classdef, properties, events, methods, arguments -" - (let ((block-type nil) - (block-beg nil)) - - (save-excursion - ;; By specifying NOERROR, returns nil if we can't move - ;; backward, which means we should stop. - - ;; backward sexp also knows to skip invlaid block starts, so we'll - ;; only land on safe blocks. - (when (and (matlab-backward-sexp t t) - (looking-at matlab-block-syntax-re)) - (setq block-type (match-string-no-properties 1) - block-beg (point))) - ) - - (cons block-type block-beg))) - (defun matlab-indent-sexp () "Indent the syntactic block starting at point." (interactive) @@ -2896,6 +2624,9 @@ Non-nil JUSTIFY-FLAG means justify comment lines as well." (forward-line 1) (beginning-of-line)))) +(defconst matlab-cline-start-skip "[ \t]*%[ \t]*" + "*The regular expression for skipping comment start.") + (defun matlab-fill-comment-line (&optional justify) "Fill the current comment line. With optional argument, JUSTIFY the comment as well." @@ -3372,13 +3103,13 @@ by `matlab-mode-vf-add-ends'" (condition-case nil (if (and (not (matlab-cursor-in-string-or-comment)) (not (matlab-block-comment-bounds)) - (or matlab-functions-have-end (not (looking-at "function")))) + (or matlab-functions-have-end (not (eq (matlab-on-keyword-p) 'decl)))) (progn (matlab-forward-sexp) (forward-word -1) - (if (not (looking-at - (concat matlab-block-end-pre-no-if "\\>"))) - (setq go nil))) + (when (not (eq (matlab-on-keyword-p) 'end)) + ;; TODO - is this possible ? + (setq go nil))) (forward-word 1)) (error (setq go nil))) @@ -3445,10 +3176,9 @@ by `matlab-mode-vf-add-ends'" "Verify/fix unstarted (or dangling end) blocks. Optional argument FAST causes this check to be skipped." (goto-char (point-max)) - (let ((go t) (expr (concat "\\<\\(" (matlab-block-end-no-function-re) - "\\)\\>"))) + (let ((go t) (expr (matlab-keyword-regex 'end))) (matlab-navigation-syntax - (while (and (not fast) go (re-search-backward expr nil t)) + (while (and (not fast) go (matlab-re-search-keyword-backward expr nil t)) (forward-word 1) (let ((s (point))) (condition-case nil From eef2e2bf012afe17a0fd5c956a655066a4e57ead Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 20 Mar 2021 20:57:04 -0400 Subject: [PATCH 099/168] matlab-maint.el: (matlab-maint-symref-this): Save buffer before running. --- matlab-maint.el | 1 + 1 file changed, 1 insertion(+) diff --git a/matlab-maint.el b/matlab-maint.el index 11ae674..399f5ef 100644 --- a/matlab-maint.el +++ b/matlab-maint.el @@ -89,6 +89,7 @@ (defun matlab-maint-symref-this () "Open a symref buffer on symbol under cursor." (interactive) + (save-buffer) (semantic-fetch-tags) (let ((ct (semantic-current-tag))) ;; Must have a tag... From 609e2f3123460fa77e7982cbef2542ef9db7fa6b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 21 Mar 2021 07:17:27 -0400 Subject: [PATCH 100/168] matlab-scan.el: (matlab-re-search-keyword-forward): No longer error if inside an unterminated list. Just fail to find the next keyword. matlab-syntax.el: (matlab-in-list-p): New (matlab-beginning-of-outer-list, matlab-end-of-outer-list): Change from let* to simplify. --- matlab-scan.el | 11 ++++++++--- matlab-syntax.el | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 2da6633..0beebc9 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1084,9 +1084,9 @@ Limit search to within BOUNDS. If keyword not found, return nil." ;; anything in a comment or string. (defun matlab-re-search-keyword-forward (regexp &optional bound noerror) "Like `re-search-forward' but will not match content in strings or comments." - (let ((ans nil) (case-fold-search nil)) + (let ((ans nil) (case-fold-search nil) (err nil)) (save-excursion - (while (and (not ans) + (while (and (not ans) (not err) (or (not bound) (< (point) bound)) (setq ans (re-search-forward regexp bound noerror))) ;; Check for simple cases that are invalid for keywords @@ -1094,7 +1094,12 @@ Limit search to within BOUNDS. If keyword not found, return nil." ;; to not waste time searching for keywords inside. (cond ((matlab-end-of-string-or-comment t) (setq ans nil)) - ((matlab-end-of-outer-list) + ((matlab-in-list-p) + (condition-case nil + ;; Protect against unterminated lists. + (matlab-end-of-outer-list) + ;; if no longer in a list, say we're done + (error (setq err t))) (setq ans nil)) ((matlab-syntax-keyword-as-variable-p) (setq ans nil)) diff --git a/matlab-syntax.el b/matlab-syntax.el index 623c31a..a349fe1 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -527,17 +527,21 @@ comment and string crossing." (up-list count nil t)) ;; will this correctly ignore comments, etc? )) +(defsubst matlab-in-list-p () + "If the cursor is in a list, return positions of the beginnings of the lists. +Returns nil if not in a list." + (nth 9 (syntax-ppss (point)))) (defsubst matlab-beginning-of-outer-list () "If the cursor is in a list, move to the beginning of outermost list. Returns non-nil if the cursor moved." - (let* ((pps (syntax-ppss (point)))) + (let ((pps (syntax-ppss (point)))) (when (nth 9 pps) (goto-char (car (nth 9 pps))) ))) (defun matlab-end-of-outer-list () "If the cursor is in a list, move to the end of the outermost list.. Returns non-nil if the cursor moved." - (let* ((pps (syntax-ppss (point)))) + (let ((pps (syntax-ppss (point)))) (when (nth 9 pps) ;; syntax-ppss doesn't have the end, so go to the front ;; and then skip forward. From 8705209b338a424defc34a4d5a562548683975b8 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 21 Mar 2021 08:18:27 -0400 Subject: [PATCH 101/168] matlab.el: (matlab-function-font-lock-keywords): Protect function arg highlighting from previous unterminated lists. --- matlab.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index aa8ce34..adff124 100644 --- a/matlab.el +++ b/matlab.el @@ -928,7 +928,10 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." "\\(\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*=[ \t\n.]*\\)?" "\\(?:[sg]et\\.\\)?\\sw+\\s-*(") '("\\s-*\\(\\sw+\\)\\s-*[,)]" - (save-excursion (matlab-scan-end-of-command)) + (save-excursion + (condition-case nil + (matlab-scan-end-of-command) + (error (point-at-eol)))) nil (1 font-lock-variable-name-face))) ;; I like variables for FOR loops From 276e8138311faa12d6a8d993135aaadaa5e88337 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 21 Mar 2021 10:19:10 -0400 Subject: [PATCH 102/168] matlab.el: (matlab-mode-vf-block-matches-forward): If this buffer has bad list syntax, don't bother doing any work. metest.el: (metest-comment-string-syntax-test): Add font lock testing to all found comments and strings. (met-fontlock-files, metest-fontlock-test): New but empty - TODO to impl this. --- matlab.el | 10 +++++ tests/metest.el | 110 ++++++++++++++++++++++++++++++++++++++------- tests/stringtest.m | 16 +++---- 3 files changed, 112 insertions(+), 24 deletions(-) diff --git a/matlab.el b/matlab.el index adff124..7ed07cf 100644 --- a/matlab.el +++ b/matlab.el @@ -3055,6 +3055,7 @@ by `matlab-mode-vf-add-ends'" ;; and if ends are optional in the first place. (filetype (matlab-guess-script-type)) ) + ;; Before checking syntax, lets re-look at the file if we were in ;; guess mode and re-assert what we should do. (cond @@ -3068,6 +3069,15 @@ by `matlab-mode-vf-add-ends'" (matlab-functions-have-end-minor-mode 1) ) + ;; If there is just bad syntax somewhere, skip it with a notice. + ((save-excursion (goto-char (point-max)) (matlab-in-list-p)) + (setq fast t) + ;; Let user fix it later + (setq matlab-functions-have-end 'guess) + (matlab-functions-have-end-minor-mode 1) + (message "Unterminated list - skipping block check")) + + ;; If we are in guess mode, but user added content, we can ;; not have a fresh new guess. ((eq matlab-functions-have-end 'guess) diff --git a/tests/metest.el b/tests/metest.el index 600d5c6..5290611 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -57,8 +57,11 @@ ;; the cahce and performance a harder problem. (metest-indents-randomize-files) (metest-run 'metest-indents-test) + (metest-run 'metest-parse-test) + + (metest-log-report (metest-log-write)) (matlab-scan-stats-print) @@ -133,37 +136,92 @@ (defun metest-comment-string-syntax-test (F) "Run a test to make sure string nd comment highlighting work." (let ((buf (metest-find-file F)) - (cnt 0)) + (cnt 0) + (noninteractive nil) ;; fake out font lock + ) (with-current-buffer buf (goto-char (point-min)) + + (let ((md (match-data))) + ;; Force font lock to throw catchable errors. + (font-lock-mode 1) + (font-lock-flush (point-min) (point-max)) + (font-lock-ensure (point-min) (point-max)) + (font-lock-fontify-region (point-min) (point-max)) + + ;; FL test 1: make sure font lock is on and match data didn't change. + (unless font-lock-mode + (metest-error "Font Lock failed to turn on.")) + ;(unless (equal md (match-data)) + ; (metest-error "Font Locking transmuted the match data")) + (when (not (get-text-property 2 'fontified)) + (metest-error "Font Lock Failure: can't run test because font lock failed to fontify region.")) + ) + + ;;(message ">> Starting string/comment detect loop in %S" (current-buffer)) - (while (re-search-forward "#\\([csvebd]\\)#" nil t) - (goto-char (match-end 1)) - (let ((md (match-data)) - (mc (match-string 1)) - (bc (metest-condition-case-error-msg (matlab-block-comment-bounds))) - (qd (metest-condition-case-error-msg (matlab-cursor-comment-string-context)))) + (while (re-search-forward "#\\([cCsSvVebd]\\)#" nil t) + (let* ((md (match-data)) + (pt (match-end 1)) + (mc (match-string-no-properties 1)) + (fnt (get-text-property pt 'face)) + (bc (metest-condition-case-error-msg (matlab-block-comment-bounds))) + (qd (metest-condition-case-error-msg (matlab-cursor-comment-string-context))) + ) + (goto-char pt) + ;; Test 1 - what are we? (unless (or (and (string= "b" mc) bc) (and (string= "v" mc) (eq 'charvector qd)) + (and (string= "V" mc) (eq 'charvector qd)) (and (string= "s" mc) (eq 'string qd)) + (and (string= "S" mc) (eq 'string qd)) (and (string= "c" mc) (eq 'comment qd)) + (and (string= "C" mc) (eq 'comment qd)) (and (string= "e" mc) (eq 'ellipsis qd)) (and (string= "d" mc) (eq 'commanddual qd)) ) (metest-error "Syntax Test Failure @ char %d: Expected %s but found %S" - (point) - (cond ((string= mc "b") "block comment") - ((string= mc "v") "charvector") - ((string= mc "s") "string") - ((string= mc "c") "comment") - ((string= mc "e") "ellipsis") - ((string= mc "d") "commanddual") - (t "unknown test token")) - qd)) + pt + (cond ((string= mc "b") "block comment") + ((string= mc "v") "charvector") + ((string= mc "V") "charvector") + ((string= mc "s") "string") + ((string= mc "S") "string") + ((string= mc "c") "comment") + ((string= mc "C") "comment") + ((string= mc "e") "ellipsis") + ((string= mc "d") "commanddual") + (t "unknown test token")) + qd)) ;; Test 2 - is match-data unchanged? (unless (equal md (match-data)) (metest-error "Syntax checking transmuted the match data")) + + ;; FL test 2 - Is the matched location fontified correctly? + (when (consp fnt) (setq fnt (car fnt))) + (unless (or (and (string= "b" mc) (eq fnt 'font-lock-comment-face)) + (and (string= "v" mc) (eq fnt 'font-lock-string-face)) + (and (string= "V" mc) (eq fnt 'matlab-unterminated-string-face)) + (and (string= "s" mc) (eq fnt 'font-lock-string-face)) + (and (string= "S" mc) (eq fnt 'matlab-unterminated-string-face)) + (and (string= "c" mc) (eq fnt 'font-lock-comment-face)) + (and (string= "C" mc) (eq fnt 'matlab-cellbreak-face)) + (and (string= "e" mc) (eq fnt 'font-lock-comment-face)) + (and (string= "d" mc) (eq fnt 'matlab-commanddual-string-face)) + ) + (metest-error "Font Lock Failure @ char %d: Expected %s but found %S" + pt + (cond ((string= mc "b") "comment face") + ((string= mc "v") "string face") + ((string= mc "V") "unterminated string face") + ((string= mc "s") "string face") + ((string= mc "S") "unterminated string face") + ((string= mc "c") "comment face") + ((string= mc "e") "comment face") + ((string= mc "d") "commanddual string face") + (t "unknown test token")) + (get-text-property pt 'face))) ;; Track (setq cnt (1+ cnt)) )) @@ -345,6 +403,26 @@ (semantic-tag-similar-p EXP ACT :documentation) ) + +(defvar met-fontlock-files '() + "List of files for running font lock tests.") + +(defvar metest-fontlock-test (cons "font lock" met-parser-files)) +(defun metest-fontlock-test (F) + "Run the semantic parsing test to make sure the parse works." + (let ((buf (metest-find-file F)) + exp act + (cnt 0)) + (with-current-buffer buf + + ;; TODO - lets add some tests. See comment-string test + + + ))) + +;;; UTILS +;; + (defun metest-find-file (file) "Read FILE into a buffer and return it. Do error checking to provide easier debugging." diff --git a/tests/stringtest.m b/tests/stringtest.m index f266baa..e399b44 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -16,8 +16,8 @@ stringscalar = "string scalar #s#"; % Comment with 'character vector #c#' in it. % Comment with "string scalar #c#" in it. -charvi = 'char vector incomplete #v# -stringi = "string scalar incomplete #s# +charvi = 'char vector incomplete #V# +stringi = "string scalar incomplete #S# % Comment with 'char vector incomplete #c# % Comment with "string scalar incomplete #c# @@ -30,16 +30,16 @@ stringcv = "string scalar with 'char vec #s#' in it"; chard = 'char vector with '' in it #v#'; stringd = "string scalar with "" in it #s#"; -chardi = 'incomplete char vector with '' in it #v# -stringdi = "incomplete string scalar with "" in it #s# +chardi = 'incomplete char vector with '' in it #V# +stringdi = "incomplete string scalar with "" in it #S# %% Strings with Comments charvc = 'char vector with % comment char #v#'; stringc = "string scalar with % comment char #s#"; -charvci = 'incomplete char vector with % comment char #v# -stringci = "incomplete string scalar with % comment char #s# +charvci = 'incomplete char vector with % comment char #V# +stringci = "incomplete string scalar with % comment char #S# charvbc = 'char vector with %{ comment char #v# %} '; stringbc = "string scalar with %{ comment char #s# %} "; @@ -149,7 +149,7 @@ else Cs = "not unreachable #s#"; end -%% Block Comments #c# +%% Block Comments #C# %{ @@ -167,7 +167,7 @@ not_commented(); % Normal comment #c# -%% Command line dual #c# +%% Command line dual #C# % Note: stuff after a symbol treated as string funcall _this is string input to function #d#_ From 3fe5b2cd1532d6314c96f041fdd5159c60e45751 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 21 Mar 2021 11:27:39 -0400 Subject: [PATCH 103/168] matlab-scan.el: (matlab-scan-stat-inc): turn off summary printing on set. --- matlab-scan.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matlab-scan.el b/matlab-scan.el index 0beebc9..b20cac3 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1238,7 +1238,9 @@ With arg, enable gathering stats, and flush old stats." (when (not sym) (set (setq sym (intern (symbol-name thing) matlab-scan-cache-stats)) 0)) (set sym (1+ (symbol-value sym)))) - (matlab-scan-stats-print 'summary))) + ;;(matlab-scan-stats-print 'summary) + ) + ) (defun matlab-scan-stats-print (&optional summary) "Display stats for scanner hits." From daa3569bc7f8d385cfc9a001725277ae932f1a35 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 21 Mar 2021 11:29:23 -0400 Subject: [PATCH 104/168] tests/metest.el: (met-kw-font-alist): New (metest-fontlock-test): Add implementation tests/mpclass.m: Instrument with font lock testing cookies. --- tests/mclass.m | 14 +++++++++- tests/metest.el | 73 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/tests/mclass.m b/tests/mclass.m index a0cf294..4cda059 100644 --- a/tests/mclass.m +++ b/tests/mclass.m @@ -1,24 +1,30 @@ % >>1 classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# +%^ ^kw ^ty ^fn ^cn ^ty ^cn ^co % !!0 % %%% class class % >>11 properties (Access='public') % #2# - +% $$$^ ^kw ^ty ^st ^co + % !!8 AP = []; % #2# +% $$$^ ^vn ^df ^co AB = 'charvec with space'; % #2# AC = "string with space and ( "; % #2# AD = fun_call(1,2); % #3# AE (1,:) double {mustBePositive} = 1; % #5# +% $$$^ ^vn ^ty ^df ^co end % <<11 % >> 111 properties (AbortSet=true, NonCopyable=true) % #2# +% $$$^ ^kw ^ty ^cn ^ty ^cn ^co % !!8 AF (1,1) char {mustBeMember(AF, {'High','Medium','Low'})} = 'Low'; % #5# +% $$$^ ^vn ^ty ^df ^st ^df ^st ^co AG (1,1) matlab.lang.OnOffSwitchState = 'on'; % #6# end % <<111 @@ -26,6 +32,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# events % !!8 Event1 + %^ ^vn Event2 end % <<112 @@ -35,6 +42,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# % >>16 function obj = mclass() + %^ ^kw ^vn ^fn ^df % !!8 obj.AB = obj.AP(1:end); @@ -47,6 +55,10 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# notify(obj,'Event1',... 'indent test'); + notify(obj, 'Event1', 'indent test'); + %^ ^df ^vn ^st ^st ^df + + % >>17 while obj.AB % #3# diff --git a/tests/metest.el b/tests/metest.el index 5290611..7a536ff 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -60,7 +60,7 @@ (metest-run 'metest-parse-test) - + (metest-run 'metest-fontlock-test) (metest-log-report (metest-log-write)) @@ -152,8 +152,8 @@ ;; FL test 1: make sure font lock is on and match data didn't change. (unless font-lock-mode (metest-error "Font Lock failed to turn on.")) - ;(unless (equal md (match-data)) - ; (metest-error "Font Locking transmuted the match data")) + ;;(unless (equal md (match-data)) + ;; (metest-error "Font Locking transmuted the match data")) (when (not (get-text-property 2 'fontified)) (metest-error "Font Lock Failure: can't run test because font lock failed to fontify region.")) ) @@ -403,22 +403,75 @@ (semantic-tag-similar-p EXP ACT :documentation) ) +(defconst met-kw-font-alist '(( "kw" . font-lock-keyword-face ) + ( "ty" . font-lock-type-face ) + ( "fn" . font-lock-function-name-face ) + ( "vn" . font-lock-variable-name-face ) + ( "cn" . font-lock-constant-face ) + ( "co" . font-lock-comment-face ) + ( "cb" . matlab-cellbreak-face ) + ( "cd" . matlab-commanddual-string-face ) + ( "st" . font-lock-string-face ) + ( "us" . matlab-unterminated-string-face ) + ( "df" . nil ) + ) + "List of testing keywords and associated faces.") + -(defvar met-fontlock-files '() +(defvar met-fontlock-files '("mclass.m") "List of files for running font lock tests.") -(defvar metest-fontlock-test (cons "font lock" met-parser-files)) +(defvar metest-fontlock-test (cons "font lock" met-fontlock-files)) (defun metest-fontlock-test (F) "Run the semantic parsing test to make sure the parse works." (let ((buf (metest-find-file F)) - exp act - (cnt 0)) + (noninteractive nil) ;; fake out font lock + (cnt 0) (fntcnt 0)) (with-current-buffer buf - ;; TODO - lets add some tests. See comment-string test - + (goto-char (point-min)) - ))) + (let ((md (match-data))) + ;; Force font lock to throw catchable errors. + (font-lock-mode 1) + (font-lock-flush (point-min) (point-max)) + (font-lock-ensure (point-min) (point-max)) + (font-lock-fontify-region (point-min) (point-max)) + + ;; FL test 1: make sure font lock is on and match data didn't change. + (unless font-lock-mode + (metest-error "Font Lock failed to turn on.")) + ;;(unless (equal md (match-data)) + ;; (metest-error "Font Locking transmuted the match data")) + (when (not (get-text-property 2 'fontified)) + (metest-error "Font Lock Failure: can't run test because font lock failed to fontify region.")) + ) + + ;; Lines that start with %^ comments are FL keyword test features. + ;; Find the line, then look for every ^ and find it's column and match + ;; to previous line's column. + (while (re-search-forward "^\\s-*%\\(?: \\$\\$\\$\\)?\\^" nil t) + (let ((next (point-at-eol)) + (prevstart (save-excursion (forward-line -1) (point-at-bol))) + ) + (while (re-search-forward "\\^\\(\\w\\w\\)\\>" (point-at-eol) t) + (let* ((col (- (match-beginning 0) (point-at-bol))) + (fk (match-string-no-properties 1)) + (pt (+ prevstart col)) + (fnt (get-text-property pt 'face)) + (exp (cdr (assoc fk met-kw-font-alist)))) + + (when (consp fnt) (setq fnt (car fnt))) + (when (not (eq exp fnt)) + (metest-error "Bad font found @ char %d: Expected %S but found %S" + pt exp fnt)) + + (setq fntcnt (1+ fntcnt)) + )) + (goto-char next) + (setq cnt (1+ cnt)))) + + (list cnt "lines with " fntcnt "fonts tested")))) ;;; UTILS ;; From d7e158f2ccf7a6801e2015687943e01d3d0ca0e0 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 22 Mar 2021 08:29:15 -0400 Subject: [PATCH 105/168] matlab-scan.el: (matlab-re-search-keyword-forward): If bad list found, jump to end of buffer. matlab-syntax.el: (matlab--scan-line-for-unterminated-string) (matlab--scan-line-comment-disable-strings): Minor optimization computing where to drop character category. (matlab-end-of-outer-list): Error if going to end of list leaves pt before starting location. --- matlab-scan.el | 6 ++++-- matlab-syntax.el | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index b20cac3..383d676 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1098,8 +1098,10 @@ Limit search to within BOUNDS. If keyword not found, return nil." (condition-case nil ;; Protect against unterminated lists. (matlab-end-of-outer-list) - ;; if no longer in a list, say we're done - (error (setq err t))) + ;; if no longer in a list, say we're done, move to end + ;; of the buffer. + (error (goto-char (point-max)) + (setq err t))) (setq ans nil)) ((matlab-syntax-keyword-as-variable-p) (setq ans nil)) diff --git a/matlab-syntax.el b/matlab-syntax.el index a349fe1..33ac2bf 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -252,8 +252,7 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ;; If we just finished and we have a double of ourselves, ;; convert those doubles into punctuation. (when (looking-at start-str) - (forward-char -1) - (matlab--put-char-category (point) 'matlab--quoted-string-syntax) + (matlab--put-char-category (1- (point)) 'matlab--quoted-string-syntax) ;; and try again. (goto-char start-char) )) @@ -275,9 +274,8 @@ Called when comments found in `matlab--scan-line-for-unterminated-string'." (save-excursion (while (re-search-forward "\\s\"" nil t) (save-excursion - (forward-char -1) - (matlab--put-char-category (point) 'matlab--transpose-syntax)) - ))) + (matlab--put-char-category (1- (point)) 'matlab--transpose-syntax) + )))) (defun matlab--scan-line-bad-blockcomment () "Scan this line for invalid block comment starts." @@ -541,12 +539,18 @@ Returns non-nil if the cursor moved." (defun matlab-end-of-outer-list () "If the cursor is in a list, move to the end of the outermost list.. Returns non-nil if the cursor moved." - (let ((pps (syntax-ppss (point)))) + (let ((pps (syntax-ppss (point))) + (start (point))) (when (nth 9 pps) ;; syntax-ppss doesn't have the end, so go to the front ;; and then skip forward. (goto-char (car (nth 9 pps))) (goto-char (scan-sexps (point) 1)) + ;; This checks for malformed buffer content + ;; that can cause this to go backwards. + (when (> start (point)) + (goto-char start) + (error "Malformed List")) ))) ;;; Useful checks for state around point. From c0d5cce467fae85543c60bb38ecbd9761bed8a0b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 22 Mar 2021 09:47:37 -0400 Subject: [PATCH 106/168] matlab-syntax.el: (matlab-end-of-string-or-comment, matlab-block-comment-bounds): If the 'end of thing' ends up before the starting point, throw an error about the syntax in the buffer being messed up. --- matlab-syntax.el | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 33ac2bf..dc284bb 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -441,7 +441,8 @@ Returns non-nil if the cursor moved." If optional ALL-COMMENTS is non-nil, then also move over all adjacent comments. Returns non-nil if the cursor moved." - (let* ((pps (syntax-ppss (point)))) + (let* ((pps (syntax-ppss (point))) + (start (point))) (when (nth 8 pps) ;; syntax-ppss doesn't have the end, so go to the front ;; and then skip forward. @@ -449,6 +450,11 @@ Returns non-nil if the cursor moved." (if (nth 3 pps) (goto-char (scan-sexps (point) 1)) (forward-comment (if all-comments 100000 1))) + ;; If the buffer is malformed, we might end up before starting pt. + ;; so error. + (when (< (point) start) + (goto-char start) + (error "Error navitaging syntax.")) t))) ;;; Block Comment handling @@ -480,6 +486,7 @@ Block comment indicators must be on a line by themselves.") Optional LINEBOUNDS specifies if returned limits are line based instead of character based." (let* ((pps (syntax-ppss (point))) + (origin (point)) (start (nth 8 pps)) (end 0)) ;; 4 is comment flag. 7 is '2' if block comment @@ -488,6 +495,9 @@ of character based." (goto-char start) (forward-comment 1) (setq end (point))) + (when (< end origin) + (goto-char origin) + (error "Error navitaging block comment syntax.")) (if linebounds ;; Bounds expanded to beginning/end of the line (cons (save-excursion From 8e2c0c89d70535f8dd993e6efc4e64b95eeaf1a6 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 22 Mar 2021 10:04:48 -0400 Subject: [PATCH 107/168] matlab.el: (matlab--change-indentation): Tweak to better support a buffer with TAB characters as indents during re-indent. --- matlab.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index 7ed07cf..4afed34 100644 --- a/matlab.el +++ b/matlab.el @@ -1752,7 +1752,8 @@ This function exists so the test harness can override it." ((< diff 0) ;; Too short - Add stuff (indent-to i)) ((<= diff ci) ;; Too much, delete some. - (delete-region (- (point) diff) (point))) + (delete-region (save-excursion (move-to-column i t) (point)) (point)) + ) (t ;; some sort of bug that wants to delete too much. Ignore. nil) )) From a29adf40af47ddafca23a9a90d9858280aebc343 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 22 Mar 2021 14:10:55 -0400 Subject: [PATCH 108/168] matlab-scan.el: (matlab--valid-mcos-keyword-point): When verifying an mcos keyword, be sure to travel to beginning of command before identifying if it is a class or end. tests/mclass_cont.m: New test for above. tests/metest.el: Add mclass_cont.m as an indentation test. --- matlab-scan.el | 2 +- tests/mclass_cont.m | 8 ++++++++ tests/metest.el | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/mclass_cont.m diff --git a/matlab-scan.el b/matlab-scan.el index 383d676..e1589c7 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -908,7 +908,7 @@ Assume basic keyword checks have already been done." (save-excursion (skip-syntax-backward "w") (forward-comment -100000) - (back-to-indentation) ;; TODO -> this should be beginning-of-command + (matlab-scan-beginning-of-command) (let ((prev (matlab--mk-keyword-node))) (or (string= (nth 1 prev) "classdef") (and (string= (nth 1 prev) "end") diff --git a/tests/mclass_cont.m b/tests/mclass_cont.m new file mode 100644 index 0000000..ee0a0cb --- /dev/null +++ b/tests/mclass_cont.m @@ -0,0 +1,8 @@ +classdef mclass_cont < otherThing & ... % !!0 This continuation can case issue for next prop block + handle %!!8 + + properties (Dependent, SetAccess = private) %!!4 + TopicName %!!8 + end %!!4 + +end %!!0 diff --git a/tests/metest.el b/tests/metest.el index 7a536ff..0615111 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -304,7 +304,7 @@ (list cnt "test"))) -(defvar met-indents-files '("indents.m" "continuations.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m") +(defvar met-indents-files '("indents.m" "continuations.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m" "mclass_cont.m") "List of files for running syntactic indentation tests.") (defun metest-indents-randomize-files () From fb674c7430663400f581cc411033e14016a645c1 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 22 Mar 2021 21:11:50 -0400 Subject: [PATCH 109/168] matlab.el: (matlab-array-continuation-indent-level): New config variable. (matlab-calculate-indentation-1): When selecting indentation for within parens, add logic for 2 indent styles depending on what kind of array continuation we find ourselves in. Use new array-continuation indent level for inside arrays. functions, arrays w/ open paren on a line by itself, and nested arrays indent under their parens. tests/indents.m: Add examples of differnet kinds of array continuations. --- matlab.el | 92 ++++++++++++++++++++++++++++++------------------- tests/indents.m | 42 +++++++++++++++------- 2 files changed, 86 insertions(+), 48 deletions(-) diff --git a/matlab.el b/matlab.el index 4afed34..b090402 100644 --- a/matlab.el +++ b/matlab.el @@ -92,6 +92,11 @@ nil (never) means that new *.m files will not enter :group 'matlab :type 'integer) +(defcustom matlab-array-continuation-indent-level 2 + "*Basic indentation after continuation within an array if no other methods are found." + :group 'matlab + :type 'integer) + (defcustom matlab-cont-requires-ellipsis t "*Specify if ellipses are required at the end of a line for continuation. Future versions of Matlab may not require ellipses ... , so a heuristic @@ -116,6 +121,39 @@ should be ok." :group 'matlab :type 'sexp) +(defcustom matlab-indent-past-arg1-functions + "[sg]et\\(_param\\)?\\|waitfor\\|notify" + "*Regex describing functions whose first arg is special. +This specialness means that all following parameters which appear on +continued lines should appear indented to line up with the second +argument, not the first argument." + :group 'matlab + :type 'string) + +(defcustom matlab-arg1-max-indent-length 15 + "*The maximum length to indent when indenting past arg1. +If arg1 is exceptionally long, then only this number of characters +will be indented beyond the open paren starting the parameter list." + :group 'matlab + :type 'integer) + +(defcustom matlab-maximum-indents '(;; = is a convenience. Don't go too far + (?= . (10 . 4)) + ;; Fns should provide hard limits + (?\( . 50) + ;; Matrix/Cell arrays + (?\[ . 20) + (?\{ . 20)) + "Alist of maximum indentations when lining up code. +Each element is of the form (CHAR . INDENT) where char is a character +the indent engine is using, and INDENT is the maximum indentation +allowed. Indent could be of the form (MAXIMUM . INDENT), where +MAXIMUM is the maximum allowed calculated indent, and INDENT is the +amount to use if MAXIMUM is reached." + :group 'matlab + :type '(repeat (cons (character :tag "Open List Character") + (sexp :tag "Number (max) or cons (max indent)")))) + (defcustom matlab-align-to-paren t "*Whether continuation lines should be aligned to the opening parenthesis. When non-nil, continuation lines are aligned to the opening parenthesis if the @@ -262,39 +300,6 @@ See `matlab-indent-function-body' variable." ;; Else, just return the variable. matlab-indent-function-body)) -(defcustom matlab-indent-past-arg1-functions - "[sg]et\\(_param\\)?\\|waitfor\\|notify" - "*Regex describing functions whose first arg is special. -This specialness means that all following parameters which appear on -continued lines should appear indented to line up with the second -argument, not the first argument." - :group 'matlab - :type 'string) - -(defcustom matlab-arg1-max-indent-length 15 - "*The maximum length to indent when indenting past arg1. -If arg1 is exceptionally long, then only this number of characters -will be indented beyond the open paren starting the parameter list." - :group 'matlab - :type 'integer) - -(defcustom matlab-maximum-indents '(;; = is a convenience. Don't go too far - (?= . (10 . 4)) - ;; Fns should provide hard limits - (?\( . 50) - ;; Matrix/Cell arrays - (?\[ . 20) - (?\{ . 20)) - "Alist of maximum indentations when lining up code. -Each element is of the form (CHAR . INDENT) where char is a character -the indent engine is using, and INDENT is the maximum indentation -allowed. Indent could be of the form (MAXIMUM . INDENT), where -MAXIMUM is the maximum allowed calculated indent, and INDENT is the -amount to use if MAXIMUM is reached." - :group 'matlab - :type '(repeat (cons (character :tag "Open List Character") - (sexp :tag "Number (max) or cons (max indent)")))) - (defcustom matlab-fill-fudge 10 "Number of characters around `fill-column' we can fudge filling. Basically, there are places that are very convenient to fill at, but @@ -1951,11 +1956,18 @@ LVL2 is a level 2 scan context with info from previous lines." (parencol (matlab-line-close-paren-inner-col lvl1)) (parenchar (matlab-line-close-paren-inner-char lvl1)) (parenpt (matlab-line-close-paren-inner-point lvl1)) + (parenindent (when parenpt + (save-excursion (goto-char parenpt) + (current-indentation)))) + (parenopt (matlab-line-close-paren-outer-point lvl1)) + ;; What shall we use to describe this for debugging? (indent-type (cond ((matlab-line-empty-p lvl1) 'empty) ((and parenchar (= parenchar ?\()) 'function-call-cont) - ((matlab-line-close-paren-outer-point lvl1) 'array-cont) + ((and parencol (= parenindent parencol)) 'array-solo-cont) + ((and parenpt (/= parenpt parenopt)) 'nested-array-cont) + (parenpt 'array-cont) (t 'code-cont))) (found-column nil) @@ -2007,7 +2019,17 @@ LVL2 is a level 2 scan context with info from previous lines." ;; indent. (if (or (not matlab-align-to-paren) (looking-at "\\.\\.\\.\\|$")) - (+ ci-boc matlab-continuation-indent-level) + (if (or (eq indent-type 'function-call-cont) + (and (not (eq indent-type 'array-solo-cont)) + (not (eq indent-type 'nested-array-cont)))) + ;; functions or an array ending on a EOL should + ;; do normal code indentation from beginning of cmd + (+ ci-boc matlab-continuation-indent-level) + ;; If in an array in an array ending on EOL should + ;; indent a wee bit + (+ parencol matlab-array-continuation-indent-level)) + ;; current column is location on original line where + ;; first bit of text is, so line up with that. (current-column) ;; TODO - this disables indentation MAXs ;; if we really want to be rid of this diff --git a/tests/indents.m b/tests/indents.m index 9c4fc9c..55974d8 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -19,7 +19,7 @@ function indents(a,b,stuff,cmddual1fake,cmddual2fake) global var2 % !!4 persistent var3 % !!4 - + locala = a; %#ok localb = b; %#ok localstuff = stuff; %#ok @@ -40,9 +40,9 @@ function indents(a,b,stuff,cmddual1fake,cmddual2fake) % $$$ !!0 % $$$ special ignore comments - + has_nested_fcn(); % !!4 - + % !!4 - after ignore comments end % Comment with end in it @@ -83,23 +83,23 @@ function B = ends_in_comments_and_strings() % !!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'; @@ -133,7 +133,7 @@ function out = array_constant_decls() 3 4; %!!14 ]; %!!12 - Csep = [ + Csep = [ 1 2; %!!8 3 4; %!!8 ]; %!!11 @@ -148,7 +148,21 @@ function out = array_constant_decls() [ 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. @@ -184,7 +198,9 @@ function out = array_constant_decls() out = { A %!!4 Blong %!!12 Csep %!!12 + nest %!!12 multinest%!!12 + cascade_long_name%!!12 Closures%!!12 dep %!!12 }; %!!10 @@ -243,7 +259,7 @@ function B = continuations_and_block_comments % !!0 %{ - !!2 + !!2 { } !!2 %} @@ -272,9 +288,9 @@ function B = continuations_and_block_comments 3 4 ]; % !!10 foo(['this is a very long string' ... %!!4 - 'with a continution to do something very exciting'])%!!9 + 'with a continution to do something very exciting']);%!!9 - set(gcf,'Position',[ 1 2 3 4],... !!4 + set(gcf,'Position',[ 1 2 3 4], ... !!4 'Color', 'red'); % !!12 B = A + 1 + 4 ... @@ -288,7 +304,7 @@ function B = continuations_and_block_comments if condition1 || ... % !!4 fcn_call(arg1, ... % !!12 - arg2) % !!21 + arg2); % !!21 line_in_if(); end % !!4 From 196843b0edfb7327c8b9686ad57df369d2f942dc Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Mon, 22 Mar 2021 21:32:59 -0400 Subject: [PATCH 110/168] matlab-shell.el: (matlab-shell-save-and-go): Enable parameters to be sent through emacsrun. toolbox/emacsrun.m: Accepts the args as a string, and build up the command to run. --- matlab-shell.el | 8 +++++++- toolbox/emacsrun.m | 17 ++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/matlab-shell.el b/matlab-shell.el index 35959e1..d9c4646 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -2179,7 +2179,13 @@ Similar to `comint-send-input'." )) ;; If not changing dir, maybe we need to use 'run' command instead? - (let ((cmd (concat "emacsrun('" dir fn-name "')"))) + (let* ((match 0) + (tmp (while (setq match (string-match "'" param match)) + (setq param (replace-match "''" t t param)) + (setq match (+ 2 match)))) + (cmd (concat "emacsrun('" dir fn-name "'" + (if (string= param "") "" (concat ", '" param "'")) + ")"))) (matlab-shell-send-command cmd))) )))) diff --git a/toolbox/emacsrun.m b/toolbox/emacsrun.m index d2cdd57..1c1f18b 100644 --- a/toolbox/emacsrun.m +++ b/toolbox/emacsrun.m @@ -1,17 +1,18 @@ -function emacsrun(mfile) +function emacsrun(mfile, args) % Run code from MFILE. % Assumes MFILE was recently edited, and proactively clears that function. % % Command sent by Emacs for save-and-go functionality - if ~exist(mfile,'file') - error('You must save your file into a location accessible by MATLAB process.'); - end - % Now figure out if shortFileName is on the path. [ fullFilePath, shortFileName ] = fileparts(mfile); onpath = ~isempty(which(shortFileName)); + if ~exist(fullFilePath,'file') + error('You must save your file into a location accessible by MATLAB process.'); + end + + % If not on the path, temporarilly switch to that directory so it and an files it references are % accessible if ~onpath @@ -19,7 +20,9 @@ function emacsrun(mfile) cd(fullFilePath); cleanup = onCleanup(@()cd(oldpath)); end - clear(shortFileName); - evalin('base',shortFileName); + + cmd = [ shortFileName args ]; + + evalin('base',cmd); end \ No newline at end of file From b97e8260afa02b4e0d52c570aa3a36ab4888786d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 23 Mar 2021 16:53:49 -0400 Subject: [PATCH 111/168] linemark.el: (linemark-group::linemark-add-entry): Wrap call to linemark-display in condition case. Catch error in eieio framework that I can't figure out. --- linemark.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/linemark.el b/linemark.el index 1115bb1..edf196e 100644 --- a/linemark.el +++ b/linemark.el @@ -275,7 +275,12 @@ Call the new entrie's activate method." (oset new-entry face (or face (oref g face))) (oset g marks (cons new-entry (oref g marks))) (if (oref g active) - (linemark-display new-entry t)) + (condition-case nil + ;; Somewhere in the eieio framework this can throw 'end of buffer' error + ;; after the display function exits. Not sure where that is, but this + ;; condition-case can capture it and allow things to keep going. + (linemark-display new-entry t) + (error nil))) new-entry) )) From 2094a595f19ef95d0cb992fd7ad520d1fd32fbac Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 23 Mar 2021 17:10:47 -0400 Subject: [PATCH 112/168] linemark.el: (linemark-find-file-hook): Wrap call to linemark display with condition-case to prevent errors from eieio. --- linemark.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/linemark.el b/linemark.el index edf196e..2711a94 100644 --- a/linemark.el +++ b/linemark.el @@ -361,7 +361,12 @@ Call the new entrie's activate method." (defun linemark-find-file-hook () "Activate all marks which can benifit from this new buffer." - (mapcar (lambda (g) (linemark-display g t)) linemark-groups)) + (mapcar (lambda (g) (condition-case nil + ;; See comment in linemark-add-entry for + ;; reasoning on this condition-case. + (linemark-display g t) + (error nil))) + linemark-groups)) (defun linemark-kill-buffer-hook () "Deactivate all entries in the current buffer." From 12ab97f02587559ab89f180992023024c4b89d5e Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 23 Mar 2021 17:32:20 -0400 Subject: [PATCH 113/168] matlab.el: (matlab-*-face): Move all faces into the font lock section of hte code. --- matlab.el | 115 +++++++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/matlab.el b/matlab.el index b090402..90a7548 100644 --- a/matlab.el +++ b/matlab.el @@ -427,64 +427,6 @@ point, but it will be restored for them." (make-variable-buffer-local 'matlab-change-current-directory) -(defface matlab-region-face - '((t :inherit region)) - "*Face used to highlight a matlab region." - :group 'matlab) - -(defvar matlab-unterminated-string-face 'matlab-unterminated-string-face - "Self reference for unterminated string face.") - -(defvar matlab-commanddual-string-face 'matlab-commanddual-string-face - "Self reference for command dual string face.") - -(defvar matlab-simulink-keyword-face 'matlab-simulink-keyword-face - "Self reference for simulink keywords.") - -(defvar matlab-nested-function-keyword-face 'matlab-nested-function-keyword-face - "Self reference for nested function/end keywords.") - -(defvar matlab-cross-function-variable-face 'matlab-cross-function-variable-face - "Self reference for cross-function variables.") - -(defvar matlab-cellbreak-face 'matlab-cellbreak-face - "Self reference for cellbreaks.") - -(defface matlab-unterminated-string-face - '((t :inherit font-lock-string-face - :underline t)) - "*Face used to highlight unterminated strings." - :group 'matlab) - -(defface matlab-commanddual-string-face - '((t :inherit font-lock-string-face - :slant italic)) - "*Face used to highlight command dual string equivalent." - :group 'matlab) - -(defface matlab-simulink-keyword-face - '((t :inherit font-lock-type-face - :underline t)) - "*Face used to highlight simulink specific functions." - :group 'matlab) - -(defface matlab-nested-function-keyword-face - '((t :inherit font-lock-keyword-face - :slant italic)) - "*Face to use for cross-function variables.") - -(defface matlab-cross-function-variable-face - '((t :weight bold - :slant italic)) - "*Face to use for cross-function variables." - :group 'matlab) - -(defface matlab-cellbreak-face - '((t :inherit font-lock-comment-face - :overline t - :bold t)) - "*Face to use for cellbreak %% lines.") - (defvar matlab-mode-abbrev-table nil "The abbrev table used in `matlab-mode' buffers.") (define-abbrev-table 'matlab-mode-abbrev-table ()) @@ -701,6 +643,63 @@ point, but it will be restored for them." ;; ;; Combine these, but do all the matching internally instead of using regexp ;; because it's just too complex for a regular expression. +(defface matlab-region-face + '((t :inherit region)) + "*Face used to highlight a matlab region." + :group 'matlab) + +(defvar matlab-unterminated-string-face 'matlab-unterminated-string-face + "Self reference for unterminated string face.") + +(defvar matlab-commanddual-string-face 'matlab-commanddual-string-face + "Self reference for command dual string face.") + +(defvar matlab-simulink-keyword-face 'matlab-simulink-keyword-face + "Self reference for simulink keywords.") + +(defvar matlab-nested-function-keyword-face 'matlab-nested-function-keyword-face + "Self reference for nested function/end keywords.") + +(defvar matlab-cross-function-variable-face 'matlab-cross-function-variable-face + "Self reference for cross-function variables.") + +(defvar matlab-cellbreak-face 'matlab-cellbreak-face + "Self reference for cellbreaks.") + +(defface matlab-unterminated-string-face + '((t :inherit font-lock-string-face + :underline t)) + "*Face used to highlight unterminated strings." + :group 'matlab) + +(defface matlab-commanddual-string-face + '((t :inherit font-lock-string-face + :slant italic)) + "*Face used to highlight command dual string equivalent." + :group 'matlab) + +(defface matlab-simulink-keyword-face + '((t :inherit font-lock-type-face + :underline t)) + "*Face used to highlight simulink specific functions." + :group 'matlab) + +(defface matlab-nested-function-keyword-face + '((t :inherit font-lock-keyword-face + :slant italic)) + "*Face to use for cross-function variables.") + +(defface matlab-cross-function-variable-face + '((t :weight bold + :slant italic)) + "*Face to use for cross-function variables." + :group 'matlab) + +(defface matlab-cellbreak-face + '((t :inherit font-lock-comment-face + :overline t + :bold t)) + "*Face to use for cellbreak %% lines.") (defvar matlab-string-start-regexp "\\(^\\|[^]})a-zA-Z0-9_.'\"]\\)" "Regexp used to represent the character before the char vector or string scalars. From d7d0766937530506439df63db726f80a64c895be Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 23 Mar 2021 19:23:08 -0400 Subject: [PATCH 114/168] matlab-syntax.el: (matlab--font-lock-syntactic-face): Add pragma face support. matlab.el: (matlab-string-start-regexp): Delete (unused) (matlab-font-lock-extend-region): Delete. block comments now supported natively. (matlab-pragma-face): New (matlab-basic-font-lock-keywords): Delete pragma face support end & ref keyword regex tweak to use \\_> (matlab-mode): Don't setup font-lock-extend-region tests/metest.el: (met-kw-font-alist): Add some new face keys to test. (met-fontlock-files): Add fontlock.m (metest-fontlock-test): Tweak how multi-fonts on one spot test. tests/fontlock.m: New suite of tests. tests/mclass.m: Add some more font lock tests. --- matlab-syntax.el | 3 ++ matlab.el | 48 +++--------------- tests/fontlock.m | 124 +++++++++++++++++++++++++++++++++++++++++++++-- tests/mclass.m | 4 ++ tests/metest.el | 23 +++++++-- 5 files changed, 152 insertions(+), 50 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index dc284bb..6f8eccf 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -317,6 +317,9 @@ Called when comments found in `matlab--scan-line-for-unterminated-string'." (cond ((and (< (nth 8 pps) (point-max)) (= (char-after (1+ (nth 8 pps))) ?\%)) 'matlab-cellbreak-face) + ((and (< (nth 8 pps) (point-max)) + (= (char-after (1+ (nth 8 pps))) ?\#)) + 'matlab-pragma-face) (t 'font-lock-comment-face)) )) diff --git a/matlab.el b/matlab.el index 90a7548..107dd6f 100644 --- a/matlab.el +++ b/matlab.el @@ -701,33 +701,10 @@ point, but it will be restored for them." :bold t)) "*Face to use for cellbreak %% lines.") -(defvar matlab-string-start-regexp "\\(^\\|[^]})a-zA-Z0-9_.'\"]\\)" - "Regexp used to represent the character before the char vector or string scalars. -The ' character has restrictions on what starts a string which is needed -when attempting to understand the current context.") - -;;; Font Lock Comment and Unreachable Code Matchers -;; -(defvar font-lock-beg) (defvar font-lock-end) ; quiet compiler. - -(defun matlab-font-lock-extend-region () - "Called by font-lock to extend the region if we are in a multi-line block." - ;; Only deal with block comments for now. - - (let* ((pos (matlab-block-comment-bounds t)) - (flb font-lock-beg) - (fle font-lock-end)) - (when pos - (setq font-lock-beg (min font-lock-beg (car pos)) - font-lock-end (max font-lock-end (cdr pos)))) - - (if (and (eq font-lock-beg flb) - (eq font-lock-end fle)) - ;; We didn't change anything. - nil - - ;; We made a change - t))) +(defface matlab-pragma-face + '((t :inherit font-lock-comment-face + :bold t)) + "*Face to use for cellbreak %% lines.") ;;; Font Lock MLINT data highlighting @@ -860,15 +837,12 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (defconst matlab-basic-font-lock-keywords (list - ;; Various pragmas should be in different colors. - ;; I think pragmas are always lower case? - '("%#\\([a-z]+\\)" (1 'bold prepend)) ;; General keywords (list (matlab-font-lock-regexp-opt matlab-keyword-list) '(0 font-lock-keyword-face)) ;; The end keyword is only a keyword when not used as an array ;; dereferencing part. - '("\\(^\\|[;,]\\)[ \t]*\\(end\\)\\b" + '("\\(^\\|[;,]\\)\\s-*\\(end\\)\\_>" 2 (if (matlab-valid-end-construct-p) font-lock-keyword-face nil)) ;; The global keyword defines some variables. Mark them. '("^\\s-*global\\s-+" @@ -883,7 +857,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (matlab-font-lock-regexp-opt matlab-constants-keyword-list) 1 font-lock-constant-face) ;; Imaginary number support - '("\\<[0-9]\\.?\\(i\\|j\\)\\>" 1 font-lock-reference-face) + '("\\<[0-9]\\.?\\(i\\|j\\)\\_>" 1 font-lock-reference-face) ) "Basic Expressions to highlight in MATLAB mode or shell.") @@ -895,7 +869,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." ;; also be alone, or have parameters after it. (list (concat "^\\s-*\\(" (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) - "\\)\\s-*[(,;%\n]") + "\\)\\_>") '(1 font-lock-keyword-face)) ;; Highlight cross function variables '(matlab-font-lock-cross-function-variables-match @@ -1231,13 +1205,6 @@ All Key Bindings: ;; This puts _ as a word constituent, ;; simplifying our keywords significantly ((?_ . "w")))) - - (setq font-lock-multiline 'undecided) - (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) - - ;; Parens mode support - (make-local-variable 'show-paren-data-function) - (setq show-paren-data-function #'matlab-show-paren-or-block) (if window-system (matlab-frame-init)) @@ -2773,6 +2740,7 @@ ARG is passed to `fill-paragraph' and will justify the text." (t (message "Paragraph Fill not supported in this context.")))) +;;; TODO - move this someplace better. (defvar gud-matlab-debug-active nil) (declare-function matlab-shell-gud-minor-mode "matlab-shell-gud") diff --git a/tests/fontlock.m b/tests/fontlock.m index 9d2b9f8..2d7d484 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -1,8 +1,27 @@ -function [a, b, c] = fontlock(input1, ... - input2, ... - input3) -% FONTLOCK testing function for Emacs & MATLAB. + +% TEST FILE FOR FONT LOCK SPECIAL WORDS +function fontlock() +%^ ^kw ^fn ^df +%^ ^co + + end +%^ ^kw + +function [ var1, var2 ] = local(input1) +%^ ^kw ^vn ^fn ^vn ^df +end + +function [a, b] = local2(input1,... + input2) +%^ ^vn +end + +% TODO - these are cross function variables, but we turn off mlint in +% tests, so these aren't tested. +function [a, b, c] = localvars(input1, input2, input3) +%^ ^kw ^vn ^fn ^vn ^vn ^vn ^df + nested(input1); q = input2; @@ -16,6 +35,101 @@ function [a, b, c] = fontlock(input1, ... c = r; end - end + +function keywordstuff() + + while true +% $$$^ ^kw ^cn + + for varname=1:10 +% $$$^ ^kw ^vn ^cn + break +% $$$^ ^kw + end + + if varname == 2 +% $$$^ ^kw ^df ^ty ^df + disp(1) + elseif varname==3 +% $$$^ ^kw ^df ^ty + disp(2) + else +% $$$^ ^kw + disp(3) + end +% $$$^ ^kw + + switch varname +% $$$^ ^kw ^cn + case 1 +% $$$^ ^kw ^cn + disp(1) + continue +% $$$^ ^kw + case 2 +% $$$^ ^kw ^cn + disp(2) + otherwise +% $$$^ ^kw + disp('other'); +% $$$^ ^df ^st + return +% $$$^ ^kw + end +% $$$^ ^kw + + try +% $$$^ ^kw + disp(1) + catch +% $$$^ ^kw + disp(2) + end +% $$$^ ^kw + + + end +end + +function dographics(value) + + f = figure; +% $$$^ ^ty + ax = axes(f); +% $$$^ ^ty + + set ( ax, 'property',value) +% $$$^^ty ^vn ^st + +end + +function dodebug() + + dbstop on dodebug +% $$$^ ^bo + + dbclear +% $$$^ ^bo + +end + +function mathstuff() + + myvar = eps + pi + nan + ans + i + NaT + true ; +% $$$^ ^df ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^df +end + +function helptest() +% HELPTEST has fancy fonts in it. +%^ ^cn + +end + +%{ +% Local Variables: +% matlab-show-mlint-warnings: nil +% End: +%} + diff --git a/tests/mclass.m b/tests/mclass.m index 4cda059..6b52741 100644 --- a/tests/mclass.m +++ b/tests/mclass.m @@ -47,6 +47,9 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# obj.AB = obj.AP(1:end); % !!12 + + unusedvar = 1; %#ok +% $$$^ ^df ^pr disp('charvect with if and for words [ in it'); % #2# @@ -141,6 +144,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# function usestuff(obj) %!!8 % Try using the methods of this object obj.properties(); %!!12 +% $$$^ ^df ^df ^co obj.methods(); %!!12 obj.events(); %!!12 obj.arguments(); %!!12 diff --git a/tests/metest.el b/tests/metest.el index 0615111..c051388 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -407,18 +407,21 @@ ( "ty" . font-lock-type-face ) ( "fn" . font-lock-function-name-face ) ( "vn" . font-lock-variable-name-face ) + ( "vc" . (font-lock-variable-name-face matlab-cross-function-variable-face) ) ( "cn" . font-lock-constant-face ) ( "co" . font-lock-comment-face ) ( "cb" . matlab-cellbreak-face ) + ( "pr" . matlab-pragma-face ) ( "cd" . matlab-commanddual-string-face ) ( "st" . font-lock-string-face ) ( "us" . matlab-unterminated-string-face ) + ( "bo" . bold ) ( "df" . nil ) ) "List of testing keywords and associated faces.") -(defvar met-fontlock-files '("mclass.m") +(defvar met-fontlock-files '("fontlock.m" "mclass.m") "List of files for running font lock tests.") (defvar metest-fontlock-test (cons "font lock" met-fontlock-files)) @@ -459,12 +462,22 @@ (fk (match-string-no-properties 1)) (pt (+ prevstart col)) (fnt (get-text-property pt 'face)) + (fnt1 (if (consp fnt) (car fnt) fnt)) + (fnt2 (if (consp fnt) (nth 1 fnt) nil)) (exp (cdr (assoc fk met-kw-font-alist)))) - (when (consp fnt) (setq fnt (car fnt))) - (when (not (eq exp fnt)) - (metest-error "Bad font found @ char %d: Expected %S but found %S" - pt exp fnt)) + (cond + ((consp exp) + (when (not (eq (car exp) fnt1)) + (metest-error "Bad font layer 1 found @ col %d: Expected %S but found %S" + col (car exp) fnt1)) + (when (not (eq (nth 1 exp) fnt2)) + (metest-error "Bad font layer 2 found @ col %d: Expected %S but found %S" + col (nth 1 exp) fnt2))) + (t + (when (not (eq exp fnt1)) + (metest-error "Bad font found @ col %d: Expected %S but found %S" + col exp fnt)))) (setq fntcnt (1+ fntcnt)) )) From 2dfa431209262e24641562eeb4da86edc57ad08c Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 23 Mar 2021 19:44:39 -0400 Subject: [PATCH 115/168] matlab.el: (matlab-toggle-read-only): Move to a better spot. --- matlab.el | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/matlab.el b/matlab.el index 107dd6f..15914ee 100644 --- a/matlab.el +++ b/matlab.el @@ -1283,6 +1283,26 @@ All Key Bindings: ;; continue. (error nil)))) + +;; Support debug mode and read only toggling. +(defvar gud-matlab-debug-active nil) +(declare-function matlab-shell-gud-minor-mode "matlab-shell-gud") + +(defun matlab-toggle-read-only (&optional arg interactive) + "Toggle read-only bit in MATLAB mode. +This looks to see if we are currently debugging, and if so re-enable +our debugging feature. +Optional argument ARG specifies if the read-only mode should be set. +INTERACTIVE is ignored." + (interactive "P") + (if (and (featurep 'matlab-shell-gud) + gud-matlab-debug-active) + ;; The debugging is active, just re-enable debugging read-only-mode + (matlab-shell-gud-minor-mode 1) + ;; Else - it is not - probably doing something else. + (call-interactively 'read-only-mode) + )) + ;;; Utilities ================================================================= @@ -2740,25 +2760,6 @@ ARG is passed to `fill-paragraph' and will justify the text." (t (message "Paragraph Fill not supported in this context.")))) -;;; TODO - move this someplace better. -(defvar gud-matlab-debug-active nil) -(declare-function matlab-shell-gud-minor-mode "matlab-shell-gud") - -(defun matlab-toggle-read-only (&optional arg interactive) - "Toggle read-only bit in MATLAB mode. -This looks to see if we are currently debugging, and if so re-enable -our debugging feature. -Optional argument ARG specifies if the read-only mode should be set. -INTERACTIVE is ignored." - (interactive "P") - (if (and (featurep 'matlab-shell-gud) - gud-matlab-debug-active) - ;; The debugging is active, just re-enable debugging read-only-mode - (matlab-shell-gud-minor-mode 1) - ;; Else - it is not - probably doing something else. - (call-interactively 'read-only-mode) - )) - ;;; Show Paren Mode support ================================================== From 859b7fde446c4e7b72a7fce1d0affbf56977216f Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 23 Mar 2021 20:29:55 -0400 Subject: [PATCH 116/168] matlab.el: (matlab-ltype-empty, matlab-ltype-comm): DELETE (matlab-ltype-continued-comm): Rename to (matlab-line-continued-comment) (matlab-lattr-block-open): Rename to (matlab-line-count-open-blocks) (matlablattr-block-close): Rename to (matlab-line-count-closed-blocks) (matlab-calculate-indentation-1): Use new names. (matlab-next-line-indentation): Use new names. (matlab-comment-return): Simplify. (matlab-comm-from-prev): New (matlab-electric-comment): Simplify (matlab-comment): Use new apis. Simpilfy. (matlab-set-comm-fill-prefix, matlab-find-convenient-line-break) (matlab-fill-paragraph, matlab-mode-vf-functionname) (matlab-mode-vf-classname, matlab-show-line-info) Use new api matlab-cgen.el: (matlab-insert-end-block, matlab-insert-next-case): Use new api for finding empty lines. matlab-complete.el: (matlab-lattr-semantics): Use new api for finding empty lines. matlab-shell.el: (matlab-shell-run-cell): Use new api for finding comment lines. --- matlab-cgen.el | 7 ++- matlab-complete.el | 35 ++++++----- matlab-shell.el | 30 ++++----- matlab.el | 149 +++++++++++++++++---------------------------- 4 files changed, 94 insertions(+), 127 deletions(-) diff --git a/matlab-cgen.el b/matlab-cgen.el index 7111d46..37306e5 100644 --- a/matlab-cgen.el +++ b/matlab-cgen.el @@ -77,7 +77,9 @@ "Insert and END block based on the current syntax. Optional argument REINDENT indicates if the specified block should be re-indented." (interactive "P") - (if (not (matlab-ltype-empty)) (progn (end-of-line) (insert "\n"))) + (when (not (matlab-line-empty-p (matlab-compute-line-context 1))) + (end-of-line) (insert "\n")) + (let ((valid t) (begin nil)) (save-excursion (condition-case nil @@ -172,7 +174,8 @@ Optional argument REINDENT indicates if the specified block should be re-indente (error (setq valid nil)))) (if (not valid) (error "Not in a switch statement"))) - (if (not (matlab-ltype-empty)) (progn (end-of-line) (insert "\n"))) + (when (not (matlab-line-empty-p (matlab-compute-line-context 1))) + (end-of-line) (insert "\n")) (indent-to 0) (insert "case ") (matlab-indent-line)) diff --git a/matlab-complete.el b/matlab-complete.el index 8dbf381..0643c31 100644 --- a/matlab-complete.el +++ b/matlab-complete.el @@ -201,24 +201,23 @@ Values are nil 'solo, 'value, and 'boolean. Boolean is a subset of value. nil means there is no semantic content (ie, string or comment.) If optional PREFIX, then return 'solo if that is the only thing on the line." - (cond ;((matlab-cursor-in-string-or-comment) - ;nil) - ((or (matlab-ltype-empty) - (and prefix (save-excursion - (beginning-of-line) - (looking-at (concat "\\s-*" prefix "\\s-*$"))))) - 'solo) - ((save-excursion - (matlab-beginning-of-command) - (looking-at "\\s-*\\(if\\|elseif\\|while\\)\\>")) - 'boolean) - ((save-excursion - (matlab-beginning-of-command) - (looking-at (concat "\\s-*\\(" (matlab-property-function) - "\\)\\>"))) - 'property) - (t - 'value))) + (cond + ((or (matlab-line-empty-p (matlab-compute-line-context 1)) + (and prefix (save-excursion + (beginning-of-line) + (looking-at (concat "\\s-*" prefix "\\s-*$"))))) + 'solo) + ((save-excursion + (matlab-beginning-of-command) + (looking-at "\\s-*\\(if\\|elseif\\|while\\)\\>")) + 'boolean) + ((save-excursion + (matlab-beginning-of-command) + (looking-at (concat "\\s-*\\(" (matlab-property-function) + "\\)\\>"))) + 'property) + (t + 'value))) ;;; Completion Framework =================================================== ;; diff --git a/matlab-shell.el b/matlab-shell.el index d9c4646..ef638fc 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -2196,20 +2196,22 @@ Similar to `comint-send-input'." (defun matlab-shell-run-cell () "Run the cell the cursor is in." (interactive) - (let ((start (save-excursion (forward-page -1) - (if (looking-at "function") - (error "You are not in a cell. Try `matlab-shell-save-and-go' instead")) - (when (matlab-ltype-comm) - ;; Skip over starting comment from the current cell. - (matlab-end-of-command) - (end-of-line) - (forward-char 1)) - (point))) - (end (save-excursion (forward-page 1) - (when (matlab-ltype-comm) - (beginning-of-line) - (forward-char -1)) - (point)))) + (let ((start (save-excursion + (forward-page -1) + (if (looking-at "function") + (error "You are not in a cell. Try `matlab-shell-save-and-go' instead")) + (when (matlab-line-comment-p (matlab-compute-line-context 1)) + ;; Skip over starting comment from the current cell. + (matlab-end-of-command) + (end-of-line) + (forward-char 1)) + (point))) + (end (save-excursion + (forward-page 1) + (when (matlab-line-comment-p (matlab-compute-line-context 1)) + (beginning-of-line) + (forward-char -1)) + (point)))) (matlab-shell-run-region start end t))) (defun matlab-shell-run-region-or-line () diff --git a/matlab.el b/matlab.el index 15914ee..f7a08f6 100644 --- a/matlab.el +++ b/matlab.el @@ -1572,17 +1572,7 @@ Travells a cross continuations" ;;; Line types, attributes, and string/comment context ==================================== -(defun matlab-ltype-empty () ; blank line - "Return t if current line is empty." - (matlab-line-empty-p (matlab-compute-line-context 1))) - -(defun matlab-ltype-comm () ; comment line - "Return t if current line is a MATLAB comment line." - ;; Looking through uses, no one uses the extra type of output - ;; this provides, so replace with very simple call. - (matlab-line-comment-p (matlab-compute-line-context 1))) - -(defun matlab-ltype-continued-comm () +(defun matlab-line-continued-comment () "Return column of previous line's comment start, or nil." (save-excursion (beginning-of-line) @@ -1595,8 +1585,8 @@ Travells a cross continuations" (forward-line -1) (matlab-line-end-comment-column (matlab-compute-line-context 1)))))) -(defun matlab-lattr-block-open (&optional eol) - "Return a number representing the number of unterminated block constructs. +(defun matlab-line-count-open-blocks (&optional eol) + "Return a the number of unterminated block constructs. This is any block, such as if or for, that doesn't have an END on this line. Optional EOL indicates a virtual end of line." (save-excursion @@ -1627,7 +1617,7 @@ Optional EOL indicates a virtual end of line." (setq v (1- v)))))) v)))) -(defun matlab-lattr-block-close (&optional start) +(defun matlab-line-count-closed-blocks (&optional start) "Return the number of closing block constructs on this line. Argument START is where to start searching from." (save-excursion @@ -1844,7 +1834,7 @@ LVL2 is a level 2 scan context with info from previous lines." ((matlab-line-comment-ignore-p lvl1) (list 'comment-ignore 0)) ;; COMMENT Continued From Previous Line - ((setq tmp (matlab-ltype-continued-comm)) ;;; TODO : REPLACE + ((setq tmp (matlab-line-continued-comment)) ;;; TODO : REPLACE (list 'comment tmp)) (t (list 'comment (+ ci matlab-comment-anti-indent)))))) @@ -2070,8 +2060,8 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (back-to-indentation) (setq lvl1 (matlab-compute-line-context 1)) - (let ((cc (matlab-lattr-block-close startpnt)) - (bc (matlab-lattr-block-open startpnt)) + (let ((cc (matlab-line-count-closed-blocks startpnt)) + (bc (matlab-line-count-open-blocks startpnt)) (end (matlab-line-end-p lvl1)) (mc (and (matlab-line-block-middle-p lvl1) 1)) (ec (and (matlab-line-block-case-p lvl1) 1)) @@ -2200,40 +2190,16 @@ Has effect of `matlab-return' with (not matlab-indent-before-return)." (defun matlab-comment-return () "Handle carriage return for MATLAB comment line." (interactive) - (cond - ((matlab-ltype-comm) - (matlab-set-comm-fill-prefix) (newline) (insert fill-prefix) - (matlab-reset-fill-prefix) (matlab-indent-line)) - ((matlab-ltype-comm) - (newline) (indent-to comment-column) - (insert matlab-comment-on-line-s)) - (t - (newline) (matlab-comment) (matlab-indent-line)))) - -(defun matlab-comm-from-prev () - "If the previous line is a `comment-line' then set up a comment on this line." - (save-excursion - ;; If the previous line is a comment-line then set the fill prefix from - ;; the previous line and fill this line. - (if (and (= 0 (forward-line -1)) (matlab-ltype-comm)) - (progn - (matlab-set-comm-fill-prefix) - (forward-line 1) (beginning-of-line) - (delete-horizontal-space) - (if (looking-at "%") (delete-char 1)) - (delete-horizontal-space) - (insert fill-prefix) - (matlab-reset-fill-prefix))))) + (newline) + (matlab-comment)) (defun matlab-electric-comment (arg) "Indent line and insert comment character. Argument ARG specifies how many %s to insert." (interactive "P") (self-insert-command (or arg 1)) - (when (matlab-ltype-comm) - (matlab-indent-line) - ;; The above seems to put the cursor on the %, not after it. - (skip-chars-forward "%"))) + (matlab-indent-line) + (skip-chars-forward "%")) (defun matlab-electric-block-comment (arg) "Indent line and insert block comment end character. @@ -2285,38 +2251,41 @@ Argument ARG specifies how many %s to insert." (defun matlab-comment () "Add a comment to the current line." (interactive) - (cond ((region-active-p) - (call-interactively #'comment-or-uncomment-region)) - ((matlab-ltype-empty) ; empty line - (matlab-comm-from-prev) - (if (matlab-ltype-comm) - (skip-chars-forward " \t%") + (let ((lvl1 (matlab-compute-line-context 1))) + (cond ((region-active-p) + (call-interactively #'comment-or-uncomment-region)) + + ((matlab-line-empty-p lvl1) ; empty line (insert matlab-comment-line-s) - (matlab-indent-line))) - ((matlab-ltype-comm) ; comment line - (matlab-comm-from-prev) - (skip-chars-forward " \t%")) - ((matlab-ltype-comm) ; code line w/ comment - (beginning-of-line) - (re-search-forward "[^%]\\(%\\)[ \t]") - (goto-char (match-beginning 1)) - (if (> (current-column) comment-column) (delete-horizontal-space)) - (if (< (current-column) comment-column) (indent-to comment-column)) - ;; Now see if the current line is too long to fit. Can we back indent? - (let ((eol-col (- (point-at-eol) (point-at-bol)))) - (when (> eol-col fill-column) - (delete-horizontal-space) - (indent-to (- comment-column (- eol-col fill-column))))) - (skip-chars-forward "% \t")) - (t ; code line w/o comment - (end-of-line) - (re-search-backward "[^ \t\n^]" 0 t) - (forward-char) - (delete-horizontal-space) - (if (< (current-column) comment-column) - (indent-to comment-column) - (insert " ")) - (insert matlab-comment-on-line-s)))) + (back-to-indentation) + (matlab-indent-line) + (skip-chars-forward " \t%")) + + ((matlab-line-comment-p lvl1) ; comment line + (back-to-indentation) + (matlab-indent-line) + (skip-chars-forward " \t%")) + + ((matlab-line-end-comment-column lvl1) ; code line w/ comment + (goto-char (matlab-line-end-comment-point lvl1)) + (if (> (current-column) comment-column) (delete-horizontal-space)) + (if (< (current-column) comment-column) (indent-to comment-column)) + ;; Now see if the current line is too long to fit. Can we back indent? + (let ((eol-col (- (point-at-eol) (point-at-bol)))) + (when (> eol-col fill-column) + (delete-horizontal-space) + (indent-to (- comment-column (- eol-col fill-column))))) + (skip-chars-forward "% \t")) + + (t ; code line w/o comment + (end-of-line) + (re-search-backward "[^ \t\n^]" 0 t) + (forward-char) + (delete-horizontal-space) + (if (< (current-column) comment-column) + (indent-to comment-column) + (insert " ")) + (insert matlab-comment-on-line-s))))) (defun matlab-comment-line-break-function (&optional soft) "Break the current line, and if in a comment, continue it. @@ -2371,7 +2340,7 @@ Argument BEG and END indicate the region to uncomment." (defun matlab-set-comm-fill-prefix () "Set the `fill-prefix' for the current (comment) line." (interactive) - (if (matlab-ltype-comm) + (if (matlab-line-comment-p (matlab-compute-line-context 1)) (setq fill-prefix (save-excursion (beginning-of-line) @@ -2412,7 +2381,7 @@ not be broken. This function will ONLY work on code." (orig (point))) (or ;; Next, if we have a trailing comment, use that. - (progn (setq pos (or (matlab-line-comment-p lvl1) ;;(matlab-ltype-comm) + (progn (setq pos (or (matlab-line-comment-p lvl1) (matlab-point-at-bol))) (goto-char pos) (if (and (> (current-column) (- fill-column matlab-fill-fudge)) @@ -2686,7 +2655,7 @@ With optional argument, JUSTIFY the comment as well." Paragraphs are always assumed to be in a comment. ARG is passed to `fill-paragraph' and will justify the text." (interactive "P") - (cond ((or (matlab-ltype-comm) + (cond ((or (matlab-line-comment-p (matlab-compute-line-context 1)) (and (matlab-cursor-in-comment) (not (matlab-line-ellipsis-p (matlab-compute-line-context 1))))) ;; We are in a comment, lets fill the paragraph with some @@ -2695,12 +2664,11 @@ ARG is passed to `fill-paragraph' and will justify the text." (let ((paragraph-separate "%%\\|%[a-zA-Z]\\|%[ \t]*$\\|[ \t]*$") (paragraph-start "%[a-zA-Z]\\|%[ \t]*$\\|[ \t]*$\\|%\\s-*\\*") (paragraph-ignore-fill-prefix nil) - (start (save-excursion (matlab-beginning-of-command) + (start (save-excursion (matlab-scan-beginning-of-command) (if (looking-at "%%") (progn (end-of-line) (forward-char 1))) - (beginning-of-line) - (point))) + (point-at-bol))) (end (save-excursion (matlab-scan-end-of-command))) (fill-prefix nil)) (matlab-set-comm-fill-prefix) @@ -2970,9 +2938,7 @@ If optional FAST is non-nil, do not perform usually lengthy checks." Optional argument FAST is ignored." (matlab-navigation-syntax (goto-char (point-min)) - (while (and (or (matlab-ltype-empty) (matlab-ltype-comm)) - (/= (matlab-point-at-eol) (point-max))) - (forward-line 1)) + (matlab-find-code-line) (let ((func nil) (bn (file-name-sans-extension (file-name-nondirectory (buffer-file-name))))) @@ -3001,10 +2967,7 @@ Optional argument FAST is ignored." Optional argument FAST is ignored." (matlab-navigation-syntax (goto-char (point-min)) - ;; Skip over whitespace. - (while (and (or (matlab-ltype-empty) (matlab-ltype-comm)) - (/= (matlab-point-at-eol) (point-max))) - (forward-line 1)) + (matlab-find-code-line) (let ((class nil) (bn (file-name-sans-extension (file-name-nondirectory (buffer-file-name))))) @@ -3270,10 +3233,10 @@ desired. Optional argument FAST is not used." " Indent Style: " (symbol-name (car indent)) )) - (if (matlab-line-ellipsis-p lvl1) - (setq msg (concat msg " w/cont"))) - (if (matlab-ltype-comm) - (setq msg (concat msg " w/comm"))) + (when (matlab-line-ellipsis-p lvl1) + (setq msg (concat msg " w/cont"))) + (when (matlab-line-end-comment-point lvl1) + (setq msg (concat msg " w/comm"))) (message "%s" msg)))) From 3df48a0b24198ec50c7a9285b09daabb03b0cc63 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 24 Mar 2021 17:24:36 -0400 Subject: [PATCH 117/168] matlab.el: (matlab-file-class-font-lock-keywords): classdef keywords work when no space between fn and paren. tests/fontlock.m: Add test for above. --- matlab.el | 4 ++-- tests/fontlock.m | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/matlab.el b/matlab.el index f7a08f6..6517eab 100644 --- a/matlab.el +++ b/matlab.el @@ -935,9 +935,9 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (defconst matlab-file-class-font-lock-keywords (list ;; Classdefs keyword and the class name - (list (concat "^\\s-*\\(classdef\\)" + (list (concat "^\\s-*\\(classdef\\)\\_>" matlab-class-attributes-list-re - "\\s-+\\(?3:\\sw+\\)") + "\\s-*\\(?3:\\sw+\\)") '(1 font-lock-keyword-face append) '(3 font-lock-function-name-face) ) diff --git a/tests/fontlock.m b/tests/fontlock.m index 2d7d484..137fd25 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -114,7 +114,7 @@ function dodebug() % $$$^ ^bo end - + function mathstuff() myvar = eps + pi + nan + ans + i + NaT + true ; @@ -127,6 +127,14 @@ function helptest() end +classdef (Abstract) myclass < handle +%^ ^kw ^ty ^fn ^cn ^ty +end + +classdef (Abstract)myclass Date: Wed, 24 Mar 2021 21:36:21 -0400 Subject: [PATCH 118/168] matlab-maint.el: (matlab-maint-reload-mode): New command. --- matlab-maint.el | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/matlab-maint.el b/matlab-maint.el index 399f5ef..eb93015 100644 --- a/matlab-maint.el +++ b/matlab-maint.el @@ -36,6 +36,7 @@ ;; compile debug cycle (define-key km [f8] 'matlab-maint-run-tests) (define-key km [f9] 'matlab-maint-compile-matlab-emacs) + (define-key km [f10] 'matlab-maint-reload-mode) ;; coding (define-key km [f7] 'matlab-maint-symref-this) ;; matlab @@ -158,6 +159,19 @@ With universal ARG, ask for the code to be run with output tracking turned on." (delete-other-windows) (goto-char (point-max))) +(defun matlab-maint-reload-mode () + "Reload matlab mode, and refresh displayed ML buffers modes." + (interactive) + (load-library "matlab-syntax") + (load-library "matlab-scan") + (load-library "matlab") + (mapc (lambda (b) (with-current-buffer b + (when (eq major-mode 'matlab-mode) + (message "Updating matlab mode in %S" b) + (matlab-mode)))) + (buffer-list (selected-frame))) + (message "loading done") + ) ;;; MATLAB SHELL tools ;; From 5c6ef4b9c5685616159bee9578a466cb00044432 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 24 Mar 2021 21:43:56 -0400 Subject: [PATCH 119/168] matlab-scan.el: (matlab-kwt-*): New regex caches. (matlab-keyword-regex): Update to use new caches. (matlab-compute-line-context-lvl-1): Support %^ ignore comment type (matlab--scan-next-keyword): New helper fcn. (matlab-re-search-keyword-forward): Add extra BONUSTEST input. Used to filter out some kinds of hints by matlab--scan-next-keyword. matlab.el: (matlab-font-lock-basic-keyword-match) (matlab-font-lock-vardecl-keyword-match) (matlab-fl-anchor-keyword) (matlab-font-lock-args-keyword-match) (matlab-font-lock-extend-region) (matlab-font-lock-anchor-set-end-limit) (matlab-font-lock-anchor-clear-end-limit) (matlab-font-lock-anchor-variable-match): New font lock support features. (matlab-basic-font-lock-keywords) (matlab-file-basic-font-lock-keywords) (matlab-function-font-lock-keywords) (matlab-file-class-font-lock-keywords): Use above new font lock keyword matchers. (matlab-mode): Setup font lock extend region feature. tests/metest.m: (met-fontlock-files): Add blocks.m tests/blocks.m, tests/fontlock.m, tests/mclass.m: Add new font lock testing instrumentation. --- matlab-scan.el | 41 +++++++++++- matlab.el | 167 ++++++++++++++++++++++++++++++++++++++--------- tests/blocks.m | 11 +++- tests/fontlock.m | 58 ++++++++-------- tests/mclass.m | 21 +++--- tests/metest.el | 5 +- 6 files changed, 227 insertions(+), 76 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index e1589c7..aed1101 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -92,6 +92,10 @@ If word is a number, it is a match-string index for the current buffer." (defvar matlab-kwt-indent nil) (defvar matlab-kwt-end nil) (defvar matlab-kwt-blocks nil) +(defvar matlab-kwt-mcos nil) +(defvar matlab-kwt-args nil) +(defvar matlab-kwt-vardecl nil) +(defvar matlab-kwt-fl-simple nil) (defun matlab-keyword-regex (types) "Find keywords that match TYPES and return optimized regexp. @@ -102,6 +106,7 @@ keyword types. These include: indent - any keyword that causes an indent end - the end keyword (that causes dedent) blocks - indent and end keywords + fl-simple - simple to highilght w/ font lock Caches some found regexp to retrieve them faster." (cond ((or (eq types nil) (eq types 'all)) @@ -114,6 +119,16 @@ Caches some found regexp to retrieve them faster." (or matlab-kwt-end (setq matlab-kwt-end (matlab--keyword-regex '(end))))) ((eq types 'blocks) (or matlab-kwt-blocks (setq matlab-kwt-blocks (matlab--keyword-regex '(end decl ctrl args mcos))))) + ((eq types 'fl-simple) + (or matlab-kwt-fl-simple (setq matlab-kwt-fl-simple (matlab--keyword-regex '(end decl ctrl mid case keyword vardecl))))) + ((eq types 'mcos) + (or matlab-kwt-mcos (setq matlab-kwt-mcos (matlab--keyword-regex '(mcos))))) + ((eq types 'args) + (or matlab-kwt-args (setq matlab-kwt-args (matlab--keyword-regex '(args))))) + ((eq types 'vardecl) + (or matlab-kwt-vardecl (setq matlab-kwt-vardecl (matlab--keyword-regex '(vardecl))))) + ((symbolp types) + (matlab--keyword-regex (list types))) (t (matlab--keyword-regex types)))) @@ -239,7 +254,8 @@ in a single call using fastest methods." 'block-start) ((looking-at "%%") 'cell-start) - ((looking-at "% \\$\\$\\$") + ;; The %^ is used in tests + ((looking-at "%\\(?:\\^\\| \\$\\$\\$\\)") 'indent-ignore) (t nil)))) @@ -948,6 +964,21 @@ Return nil if no valid block under pt." nil (push thiskeyword providedstate))))) +;;; Keyword Scanning +;; +;; Use for font locking +(defun matlab--scan-next-keyword (keyword-type limit) + "Scan for the next keyword of KEYWORD-TYPE and stop. +Sets match data to be around the keyword. +If nothing is found before LIMIT, then stop and return nil." + (let* ((regex (matlab-keyword-regex keyword-type)) + (filter (cond ((eq keyword-type 'mcos) + #'matlab--valid-mcos-keyword-point) + ((eq keyword-type 'args) + #'matlab--valid-arguments-keyword-point) + (t nil)))) + (matlab-re-search-keyword-forward regex limit t filter))) + ;;; Block Scanning ;; (defun matlab--scan-block-forward (&optional bounds state) @@ -1082,8 +1113,10 @@ Limit search to within BOUNDS. If keyword not found, return nil." ;; ;; These utilities will simplify searching for code bits by skipping ;; anything in a comment or string. -(defun matlab-re-search-keyword-forward (regexp &optional bound noerror) - "Like `re-search-forward' but will not match content in strings or comments." +(defun matlab-re-search-keyword-forward (regexp &optional bound noerror bonustest) + "Like `re-search-forward' but will not match content in strings or comments. +If BONUSTEST is a function, use it to test each match if it is valid. If not +then skip and keep searching." (let ((ans nil) (case-fold-search nil) (err nil)) (save-excursion (while (and (not ans) (not err) @@ -1105,6 +1138,8 @@ Limit search to within BOUNDS. If keyword not found, return nil." (setq ans nil)) ((matlab-syntax-keyword-as-variable-p) (setq ans nil)) + ((and bonustest (save-match-data (not (funcall bonustest)))) + (setq ans nil)) ))) (when ans (goto-char ans) (matlab--mk-keyword-node)))) diff --git a/matlab.el b/matlab.el index 6517eab..b5c3a7e 100644 --- a/matlab.el +++ b/matlab.el @@ -708,6 +708,10 @@ point, but it will be restored for them." ;;; Font Lock MLINT data highlighting +;; TODO - THE BELOW LOOKS BROKEN TO ME. +;; - these are used in font lock, but are hand adding overlays +;; - and returning no matches - but the font lock keywords try to add +;; - a font. NEEDS FIX (defun matlab-font-lock-nested-function-keyword-match (limit) "Find next nested function/end keyword for font-lock. Argument LIMIT is the maximum distance to search." @@ -825,6 +829,90 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (mapconcat (lambda (s) s) keywordlist "\\|")) "\\)\\>")) +;;; Font lock keyword handlers +;; +(defun matlab-font-lock-basic-keyword-match (limit) + "Font lock matcher for basic keywords. +Fails to match when keywords show up as variables, etc." + (matlab--scan-next-keyword 'fl-simple limit)) + +(defun matlab-font-lock-vardecl-keyword-match (limit) + "Font lock matcher for mcos keywords. +Fails to match when keywords show up as variables, etc." + (matlab--scan-next-keyword 'vardecl limit)) + +(defvar matlab-fl-anchor-keyword nil) +(defun matlab-font-lock-mcos-keyword-match (limit) + "Font lock matcher for mcos keywords. +Fails to match when keywords show up as variables, etc." + (and (eq matlab-functions-have-end 'class) + (setq matlab-fl-anchor-keyword + (matlab--scan-next-keyword 'mcos limit)))) + +(defun matlab-font-lock-args-keyword-match (limit) + "Font lock matcher for mcos keywords. +Fails to match when keywords show up as variables, etc." + (setq matlab-fl-anchor-keyword + (matlab--scan-next-keyword 'args limit))) + +(defvar font-lock-beg) (defvar font-lock-end) ; quiet compiler. +(defun matlab-font-lock-extend-region () + "Called by font-lock to extend the region for multiline expressions. +Supports expressions like arguments and property blocks with anchored +color support." + (save-excursion + (let* ((flb font-lock-beg) + (fle font-lock-end) + (tmp (matlab--scan-block-backward-up (window-start))) + (blockmatch (when (not tmp) (matlab--mk-keyword-node)))) + (when (and (member (nth 1 blockmatch) '("properties" "events" "arguments")) + (matlab--valid-keyword-node blockmatch)) + (setq font-lock-beg (min font-lock-beg (point-at-bol))) + (when (not (matlab--scan-next-keyword 'all (window-end))) + (setq font-lock-end (max font-lock-end (point-at-eol))))) + + (if (and (eq font-lock-beg flb) + (eq font-lock-end fle)) + ;; We didn't change anything. + nil + + ;; We made a change + t)))) + +(defvar ml-fl-anchor-limit nil) +(defun matlab-font-lock-anchor-set-end-limit () + "Set the end limit for anchored matchers." + (save-excursion + ;; next keyword is faster, plus if someone is in the middle of typing + ;; a new block, prevents going too far into the distance. + (matlab--scan-next-keyword 'all (point-max)) + (forward-word -1) + (setq ml-fl-anchor-limit (point)))) + +(defun matlab-font-lock-anchor-clear-end-limit () + "Clear the end limit for anchored matchers." + (setq ml-fl-anchor-limit nil)) + +(defun matlab-font-lock-anchor-variable-match (limit) + "After finding a keyword like PROPERTIES or ARGUMENTS, match vars. +This matcher will handle a range of variable features." + (when (member (nth 1 matlab-fl-anchor-keyword) + '("properties" "events" "arguments")) + (let* ((match (re-search-forward "^\\s-+\\(\\w+\\)\\_>" ml-fl-anchor-limit t)) + ;; Save this match so we can do a 2nd anchored search for a data type. + (md1 (list (match-beginning 1) (match-end 1))) + (tm (looking-at + "\\s-*\\(\\(?:([^\n)]+)\\)?\\s-*\\(?:\\w+\\|\\.\\)*\\)\\s-*\\($\\|[.%{=]\\)")) + (tm1 (if tm (list (match-beginning 1) (match-end 1)) + ;; The below is a cheat to not highlight anything but + ;; still supply the match data for this optional piece. + (list (nth 1 md1) (nth 1 md1)))) + (newmdata (append md1 md1 tm1))) + (when match + (goto-char (apply 'max newmdata)) + (set-match-data newmdata) + t)))) + ;;; Font Lock keyword handling ;; ;; Many parts of the keyword handling are shared with matlab-shell. @@ -838,16 +926,8 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (defconst matlab-basic-font-lock-keywords (list ;; General keywords - (list (matlab-font-lock-regexp-opt matlab-keyword-list) - '(0 font-lock-keyword-face)) - ;; The end keyword is only a keyword when not used as an array - ;; dereferencing part. - '("\\(^\\|[;,]\\)\\s-*\\(end\\)\\_>" - 2 (if (matlab-valid-end-construct-p) font-lock-keyword-face nil)) - ;; The global keyword defines some variables. Mark them. - '("^\\s-*global\\s-+" - ("\\(\\w+\\)\\(\\s-*=[^,; \t\n]+\\|[, \t;]+\\|$\\)" - nil nil (1 font-lock-variable-name-face))) + '(matlab-font-lock-basic-keyword-match + (0 font-lock-keyword-face)) ;; Handle graphics stuff (list (matlab-font-lock-regexp-opt matlab-handle-graphics-list) @@ -865,12 +945,12 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (append matlab-basic-font-lock-keywords (list - ;; Keywords that should be the first word on a line AND - ;; also be alone, or have parameters after it. - (list (concat "^\\s-*\\(" - (matlab-font-lock-regexp-opt matlab-keyword-first-on-line-list) - "\\)\\_>") - '(1 font-lock-keyword-face)) + ;; MCOS keywords like properties, methods, events + '(matlab-font-lock-mcos-keyword-match + (1 font-lock-keyword-face)) + ;; ARGUMENTS keyword + '(matlab-font-lock-args-keyword-match + (1 font-lock-keyword-face)) ;; Highlight cross function variables '(matlab-font-lock-cross-function-variables-match (1 matlab-cross-function-variable-face prepend)) @@ -887,11 +967,11 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (list ;; defining a function, a (possibly empty) list of assigned variables, ;; function name, and an optional (possibly empty) list of input variables - (list (concat "^\\s-*\\(function\\)\\>[ \t\n.]*" + (list (concat "^\\s-*\\(function\\)\\_>[ \t\n.]*" "\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*" "=[ \t\n.]*\\(\\(?:[sg]et\\.\\)?\\sw+\\)[ \t\n.]*" matlab-function-arguments) - '(1 font-lock-keyword-face append) + ;;'(1 font-lock-keyword-face append) - handled as keyword '(2 font-lock-variable-name-face append) '(3 font-lock-function-name-face prepend)) ;; defining a function, a function name, and an optional (possibly @@ -899,7 +979,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (list (concat "^\\s-*\\(function\\)[ \t\n.]+" "\\(\\(?:[sg]et\\.\\)?\\sw+\\)[ \t\n.]*" matlab-function-arguments) - '(1 font-lock-keyword-face append) + ;; '(1 font-lock-keyword-face append) - handled as keyword '(2 font-lock-function-name-face prepend)) ;; Anchor on the function keyword, highlight params (list (concat "^\\s-*function\\>[ \t\n.]*" @@ -912,6 +992,18 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (error (point-at-eol)))) nil (1 font-lock-variable-name-face))) + ;; ARGUMENTS have variables to highlight + '(matlab-font-lock-args-keyword-match + (matlab-font-lock-anchor-variable-match ;; matcher fcn + (matlab-font-lock-anchor-set-end-limit) ;; pre forms + (matlab-font-lock-anchor-clear-end-limit) ;; post forms + (1 font-lock-variable-name-face t) + (2 font-lock-type-face t) + )) + ;; VARDECL keywords + '(matlab-font-lock-vardecl-keyword-match + ("\\(\\w+\\)\\(\\s-*=[^,; \t\n]+\\|[, \t;]+\\|$\\)" + nil nil (1 font-lock-variable-name-face))) ;; I like variables for FOR loops '("\\<\\(\\(?:par\\)?for\\)\\s-+\\(\\sw+\\)\\s-*=\\s-*\ \\(\\([^\n,;%(]+\\|([^\n%)]+)\\)+\\)" @@ -919,8 +1011,8 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (2 font-lock-variable-name-face append) (3 font-lock-reference-face append)) ;; Items after a switch statements are cool - '("\\<\\(case\\|switch\\)\\s-+\\({[^}\n]+}\\|[^,%\n]+\\)" - (1 font-lock-keyword-face) (2 font-lock-reference-face)) + '("\\_<\\(case\\|switch\\)\\_>\\s-+\\({[^}\n]+}\\|[^,%\n]+\\)" + (2 font-lock-reference-face)) ;; set_param and waitfor have input variables that can be highlighted. (list (concat "\\<" matlab-indent-past-arg1-functions "\\s-*") '("(\\s-*\\(\\w+\\)\\s-*\\(,\\|)\\)" nil nil @@ -938,7 +1030,7 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." (list (concat "^\\s-*\\(classdef\\)\\_>" matlab-class-attributes-list-re "\\s-*\\(?3:\\sw+\\)") - '(1 font-lock-keyword-face append) + ;; '(1 font-lock-keyword-face append) - handled as keyword '(3 font-lock-function-name-face) ) ;; Classdef anchor for highlighting all the base classes in inherits from @@ -952,17 +1044,26 @@ Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." '("\\(\\sw+\\)\\s-*\\(=\\s-*[^,)]+\\)?" nil nil (1 font-lock-type-face) )) + ;; PROPERY, EVENTS, etc have variables to highlight + '(matlab-font-lock-mcos-keyword-match + (matlab-font-lock-anchor-variable-match ;; matcher fcn + (matlab-font-lock-anchor-set-end-limit) ;; pre forms + (matlab-font-lock-anchor-clear-end-limit) ;; post forms + (1 font-lock-variable-name-face t) + (2 font-lock-type-face t) + )) + ;; Properties can have a type syntax after them - '("^\\s-*\\w+\\s-*\\(([:0-9,]+)\\s-*[^{=\n]+\\)" - (1 font-lock-type-face nil nil)) + ;;'("^\\s-*\\w+\\s-*\\(([:0-9,]+)\\s-*[^{=\n]+\\)" + ;; (1 font-lock-type-face nil nil)) ;; Properties blocks are full of variables - '("^\\s-*\\(properties\\|events\\|arguments\\)\\s-*[(,;%\n]" - ("^\\s-*\\(\\sw+\\)\\>" ;; This part matches the variable - ;; extend region to match in - (save-excursion (matlab-forward-sexp t) (beginning-of-line) (point)) - nil - (1 font-lock-variable-name-face t)) - ) + ;;'("^\\s-*\\(properties\\|events\\|arguments\\)\\s-*[(,;%\n]" + ;; ("^\\s-*\\(\\sw+\\)\\>" ;; This part matches the variable + ;; ;; extend region to match in + ;; (save-excursion (matlab-forward-sexp t) (beginning-of-line) (point)) + ;; nil + ;; (1 font-lock-variable-name-face t)) + ;; ) ) "List of font-lock keywords used when an MATLAB file contains a class.") @@ -1205,7 +1306,9 @@ All Key Bindings: ;; This puts _ as a word constituent, ;; simplifying our keywords significantly ((?_ . "w")))) - + (setq font-lock-multiline 'undecided) + (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) + (if window-system (matlab-frame-init)) ;; If first function is terminated with an end statement, then functions have diff --git a/tests/blocks.m b/tests/blocks.m index b40db76..1d6de3b 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -78,16 +78,21 @@ classdef blocks < handle %>>20 function events=arguments(arguments)%!!8 arguments, arguments(:,:) {mustBeNumeric}, end %!!12 - +%^ ^kw ^df ^ty ^df ^kw ^co enumeration ... %!!12 + ...%^ ^df = arguments; +%^ ^df if enumeration > 0 %!!12 arguments = -enumeration; %!!16 +%^ ^df ^ty ^df end %!!12 events ... %!!12 = arguments + 1; +%^ ^df + end %<<20 %>>21 @@ -156,8 +161,8 @@ classdef blocks < handle function tightcomments(~)%!!8 if condition%!!12 - switch thing%!!16 - case b%!!18 + switch thing %!!16 + case b %!!18 end%!!16 end%!!12 end%!!8 diff --git a/tests/fontlock.m b/tests/fontlock.m index 137fd25..251ca4c 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -4,6 +4,12 @@ function fontlock() %^ ^kw ^fn ^df %^ ^co + persistent var1 % !!4 +%^ ^kw ^vn ^co + + global var2 % !!4 +%^ ^kw ^vn ^co + end %^ ^kw @@ -41,53 +47,53 @@ end function keywordstuff() while true -% $$$^ ^kw ^cn +%^ ^kw ^cn for varname=1:10 -% $$$^ ^kw ^vn ^cn +%^ ^kw ^vn ^cn break -% $$$^ ^kw +%^ ^kw end if varname == 2 -% $$$^ ^kw ^df ^ty ^df +%^ ^kw ^df ^ty ^df disp(1) elseif varname==3 -% $$$^ ^kw ^df ^ty +%^ ^kw ^df ^ty disp(2) else -% $$$^ ^kw +%^ ^kw disp(3) end -% $$$^ ^kw +%^ ^kw switch varname -% $$$^ ^kw ^cn +%^ ^kw ^cn case 1 -% $$$^ ^kw ^cn +%^ ^kw ^cn disp(1) continue -% $$$^ ^kw +%^ ^kw case 2 -% $$$^ ^kw ^cn +%^ ^kw ^cn disp(2) otherwise -% $$$^ ^kw +%^ ^kw disp('other'); -% $$$^ ^df ^st +%^ ^df ^st return -% $$$^ ^kw +%^ ^kw end -% $$$^ ^kw +%^ ^kw try -% $$$^ ^kw +%^ ^kw disp(1) catch -% $$$^ ^kw +%^ ^kw disp(2) end -% $$$^ ^kw +%^ ^kw end @@ -96,29 +102,29 @@ end function dographics(value) f = figure; -% $$$^ ^ty +%^ ^ty ax = axes(f); -% $$$^ ^ty +%^ ^ty set ( ax, 'property',value) -% $$$^^ty ^vn ^st +%^ ^ty ^vn ^st end function dodebug() dbstop on dodebug -% $$$^ ^bo +%^ ^bo dbclear -% $$$^ ^bo +%^ ^bo end function mathstuff() myvar = eps + pi + nan + ans + i + NaT + true ; -% $$$^ ^df ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^df +%^ ^df ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^df end function helptest() @@ -128,11 +134,11 @@ function helptest() end classdef (Abstract) myclass < handle -%^ ^kw ^ty ^fn ^cn ^ty +%^ ^kw ^ty ^fn ^ty ^cn end classdef (Abstract)myclass>11 properties (Access='public') % #2# -% $$$^ ^kw ^ty ^st ^co +%^ ^kw ^ty ^st ^co % !!8 AP = []; % #2# -% $$$^ ^vn ^df ^co +%^ ^vn ^df ^co AB = 'charvec with space'; % #2# AC = "string with space and ( "; % #2# AD = fun_call(1,2); % #3# AE (1,:) double {mustBePositive} = 1; % #5# -% $$$^ ^vn ^ty ^df ^co +%^ ^vn ^ty ^df ^co end % <<11 % >> 111 properties (AbortSet=true, NonCopyable=true) % #2# -% $$$^ ^kw ^ty ^cn ^ty ^cn ^co +%^ ^kw ^ty ^cn ^ty ^cn ^co % !!8 AF (1,1) char {mustBeMember(AF, {'High','Medium','Low'})} = 'Low'; % #5# -% $$$^ ^vn ^ty ^df ^st ^df ^st ^co +%^ ^vn ^ty ^df ^st ^df ^st ^co AG (1,1) matlab.lang.OnOffSwitchState = 'on'; % #6# +%^ ^vn ^ty ^ty ^st ^co end % <<111 % >> 112 events % !!8 Event1 - %^ ^vn +%^ ^vn Event2 end % <<112 @@ -49,7 +50,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# % !!12 unusedvar = 1; %#ok -% $$$^ ^df ^pr +%^ ^df ^pr disp('charvect with if and for words [ in it'); % #2# @@ -59,7 +60,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# 'indent test'); notify(obj, 'Event1', 'indent test'); - %^ ^df ^vn ^st ^st ^df +%^ ^df ^vn ^st ^st ^df % >>17 @@ -144,13 +145,13 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# function usestuff(obj) %!!8 % Try using the methods of this object obj.properties(); %!!12 -% $$$^ ^df ^df ^co +%^ ^df ^df ^co obj.methods(); %!!12 obj.events(); %!!12 obj.arguments(); %!!12 obj.enumeration(); %!!12 end %!!8 - + end %!!4 end % <<1 diff --git a/tests/metest.el b/tests/metest.el index c051388..154498b 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -407,7 +407,8 @@ ( "ty" . font-lock-type-face ) ( "fn" . font-lock-function-name-face ) ( "vn" . font-lock-variable-name-face ) - ( "vc" . (font-lock-variable-name-face matlab-cross-function-variable-face) ) + ( "vc" . (font-lock-variable-name-face + matlab-cross-function-variable-face) ) ( "cn" . font-lock-constant-face ) ( "co" . font-lock-comment-face ) ( "cb" . matlab-cellbreak-face ) @@ -421,7 +422,7 @@ "List of testing keywords and associated faces.") -(defvar met-fontlock-files '("fontlock.m" "mclass.m") +(defvar met-fontlock-files '("fontlock.m" "mclass.m" "blocks.m") "List of files for running font lock tests.") (defvar metest-fontlock-test (cons "font lock" met-fontlock-files)) From 7b49b6f4476284af7a99d437cacd4d3087629d47 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 24 Mar 2021 22:08:52 -0400 Subject: [PATCH 120/168] matlab-syntax.el: (matlab-syntax-support-command-dual): Default to t (matlab-syntax-commanddual-functions): List of functions commonly used with command line dual. (matlab--scan-line-for-command-dual): Use above lists instead of anti-keyword detection. tests/fontlock.m, tests/indents.m: Add tests for command dual. tests/stringtest.m: Command dual test updated to use 'disp' --- matlab-syntax.el | 20 ++++++++++++++------ tests/fontlock.m | 6 +++--- tests/indents.m | 22 ++++++++++++++++++++-- tests/stringtest.m | 4 ++-- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 6f8eccf..96237f2 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -30,7 +30,7 @@ (require 'matlab-compat) ;;; Code: -(defvar matlab-syntax-support-command-dual nil +(defvar matlab-syntax-support-command-dual t "Non-nil means to support command dual for indenting and syntax highlight. Does not work well in classes with properties with datatypes.") (make-variable-buffer-local 'matlab-syntax-support-command-dual) @@ -208,16 +208,24 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (forward-line 1)) ))) -(declare-function matlab-keyword-p "matlab-scan") +(defconst matlab-syntax-commanddual-functions + '("warning" "disp" + ;; debug + "dbstop" "dbclear" + ;; Graphics + "print" "xlim" "ylim" "zlim" "grid" "hold" "box") + "Functions that are commonly used with commandline dual") +(defconst matlab-cds-regex (regexp-opt matlab-syntax-commanddual-functions 'symbols)) (defun matlab--scan-line-for-command-dual (&optional debug) "Scan this line for command line duality strings." ;; Note - add \s$ b/c we'll add that syntax to the first letter, and it ;; might still be there during an edit! - (when (looking-at "^\\s-*\\([a-zA-Z_]\\(?:\\w\\|\\s_\\)*\\)\\s-+\\(\\s$\\|\\w\\|\\s_\\)") - ;; This is likely command line dual for a function. - (when (not (matlab-keyword-p 1)) - (goto-char (match-beginning 2))))) + (when (looking-at + (concat "^\\s-*" + matlab-cds-regex + "\\s-+\\(\\s$\\|\\w\\|\\s_\\)")) + (goto-char (match-beginning 2)))) (matlab--syntax-symbol matlab--transpose-syntax '(3 . nil) ;; 3 = symbol "Treat ' as non-string when used as transpose.") diff --git a/tests/fontlock.m b/tests/fontlock.m index 251ca4c..a2433df 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -112,9 +112,9 @@ function dographics(value) end function dodebug() - - dbstop on dodebug -%^ ^bo + + dbstop in dodebug +%^ ^bo ^cd dbclear %^ ^bo diff --git a/tests/indents.m b/tests/indents.m index 55974d8..9409bb0 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -304,7 +304,7 @@ function B = continuations_and_block_comments if condition1 || ... % !!4 fcn_call(arg1, ... % !!12 - arg2); % !!21 + arg2) % !!21 line_in_if(); end % !!4 @@ -336,4 +336,22 @@ function b=function_end_same_line(a), b=a; end %!!0 function function_after_end_same_line()%!!0 %!!0 disp('foo');%!!4 -end%!!0 \ No newline at end of file + + 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 + +end \ No newline at end of file diff --git a/tests/stringtest.m b/tests/stringtest.m index e399b44..bc7812b 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -170,8 +170,8 @@ not_commented(); %% Command line dual #C# % Note: stuff after a symbol treated as string -funcall _this is string input to function #d#_ -fun_call _this is also string input to a function #d#_ +disp _this is string input to function #d#_ +disp _this is also string input to a function #d#_ %{ % Local Variables: From 9a8784af2afc2938b039f0c83b98c4ff481b6670 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Mar 2021 19:02:27 -0400 Subject: [PATCH 121/168] matlab-scan.el: (matlab-previous-code-line): In case of no comments, also hand skip over whitespace charts. tests/metest.el: Force empty lines to have no spaces, other lines stay the same. This will exercise bug found above. tests/indents.m: Fix mlint error. --- matlab-scan.el | 5 ++++- tests/indents.m | 2 ++ tests/metest.el | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index aed1101..5bb2141 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -676,7 +676,10 @@ If LVL2 is nil, compute it." (matlab-scan-stat-inc 'codemiss) (beginning-of-line) (when (not (bobp)) - (forward-comment -100000) ;; Skip over all whitespace and comments. + ;; Skip over all comments and intervening whitespace + (forward-comment -100000) + ;; If no comments, then also skip over these whitespace chars. + (skip-chars-backward " \t\n") (setq prev (matlab-compute-line-context 1)) (setcar (nthcdr mlf-previous-code lvl2) prev))) ;; else record a cache hit diff --git a/tests/indents.m b/tests/indents.m index 9409bb0..9bb9c51 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -354,4 +354,6 @@ function debug_cmd_dual () %!!4 + debug_cmd_dual(); %!!4 + end \ No newline at end of file diff --git a/tests/metest.el b/tests/metest.el index 154498b..aa7a9fb 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -317,7 +317,9 @@ (goto-char (point-min)) (while (not (eobp)) (beginning-of-line) - (matlab--change-indentation 3) ;;(random 12) + (if (looking-at "^\\s-*$") + (matlab--change-indentation 0) + (matlab--change-indentation 3)) ;;(random 13)? (forward-line 1) ) ;; And don't delete - leave it to find for the next test. From ed9d811014f1c312efe71519641cdef37d91b307 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Mar 2021 22:43:13 -0400 Subject: [PATCH 122/168] matlab.el: (matlab--calc-indent): Stop computing next line indent. That is done elsewhere. (matlab--previous-line-indent-recommendation): New, from matlab--calc-indent old logic. (matlab-calculate-indentation): DELETED - but renamed (matlab-calculate-indentation-1): Renamed to old deleted fcn. Deleted input current-indentation. Replaced all uses of old input with call to `matlab--previous-line-indent-recommendation'. This means many more cases of indentation don't need to compute their indent from the previous line. Also refactored some sections to use cond statement instead of nested if. Revamped continuations block to not create return inline, but to use tmp for indent and return data is now more obvious. --- matlab.el | 330 +++++++++++++++++++++++++++--------------------------- 1 file changed, 168 insertions(+), 162 deletions(-) diff --git a/matlab.el b/matlab.el index b5c3a7e..a746989 100644 --- a/matlab.el +++ b/matlab.el @@ -1850,52 +1850,42 @@ This function exists so the test harness can override it." (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) ;; The first step is to find the current indentation. ;; This is defined to be zero if all previous lines are empty. - (let* ((ci (cond - ((save-excursion (beginning-of-line) (bobp)) - ;; Beginning of buffer - do no work, just return 0. - 0) - ((matlab-line-in-array lvl2) - ;; If we are inside an array continuation, then we shouldn't - ;; need to do anything complicated here b/c we'll just ignore - ;; the returned value in the next step. Return current indentation - ;; of the previous non-empty line. - (matlab-line-indentation (matlab-previous-nonempty-line lvl2))) - - (t - ;; Else, the previous line might recommend an indentation based - ;; on it's own context, like being a block open or continuation. - (let ((prevcmd (or (matlab-previous-code-line lvl2) - (matlab-previous-line lvl2)))) - (matlab-with-context-line prevcmd - (matlab-next-line-indentation prevcmd))))) - ) - - ;; Compute this line's indentation based on recommendation of previous - ;; line. - (sem (matlab-calculate-indentation ci lvl2))) + (let* ((sem (matlab-calculate-indentation lvl2))) (when debug-sym (set debug-sym sem)) ;; simplistic (nth 1 sem))) +(defun matlab--previous-line-indent-recommendation (lvl2) + "Return the indentation recommendation from the previous line of CODE. +Uses the lvl2 context of the current line of code it scans backward from. +This function scans backward over blank lines and comments to find a +line of code. It then scans that line and recommends either: + - same indentation - if just boring old code. + - indent more - if has control block openings on it." + (cond + ((save-excursion (beginning-of-line) (bobp)) + ;; Beginning of buffer - do no work, just return 0. + 0) + ((matlab-line-in-array lvl2) + ;; If we are inside an array continuation, then we shouldn't + ;; need to do anything complicated here b/c we'll just ignore + ;; the returned value in the next step. Return current indentation + ;; of the previous non-empty line. + (matlab-line-indentation (matlab-previous-nonempty-line lvl2))) + (t + ;; Else, the previous line might recommend an indentation based + ;; on it's own context, like being a block open or continuation. + (let ((prevcmd (or (matlab-previous-code-line lvl2) + (matlab-previous-line lvl2)))) + (matlab-with-context-line prevcmd + (matlab-next-line-indentation prevcmd)))))) + + (defconst matlab-functions-have-end-should-be-true "This end closes a function definition.\nDo you want functions to have ends? " "Prompt the user about whether to change `matlab-functions-have-end'.") -(defun matlab-calculate-indentation (current-indentation &optional lvl2) - "Calculate out the indentation of the current line. -Return a list of descriptions for this line. Return format is: - '(TYPE DEPTHNUMBER) -where TYPE is one of (comment, code, function, blockstart, blockmid, -blockendless, blockend) DEPTHNUMBER is how many characters to indent -this line. - Argument CURRENT-INDENTATION is what the previous line thinks -this line's indentation should be. See `matlab-next-line-indentation'." - (matlab-navigation-syntax - ;; TODO - make this compute obsolete. - (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) - ;; Do the compute - (matlab-calculate-indentation-1 current-indentation lvl2))) (defun matlab--maybe-yes-or-no-p (prompt noninteractive-default) "When in non-interactive mode run (yes-or-no-p prompt), @@ -1905,12 +1895,16 @@ otherwise return NONINTERACTIVE-DEFAULT" (yes-or-no-p prompt))) -(defun matlab-calculate-indentation-1 (current-indentation lvl2) - "Do the indentation work of `matlab-calculate-indentation'. -Argument CURRENT-INDENTATION is what the previous line recommends for indentation. -LVL2 is a level 2 scan context with info from previous lines." - (let ((ci current-indentation) - (lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) +(defun matlab-calculate-indentation (&optional lvl2) + "Calculate out the indentation of the current line. +Return a list of descriptions for this line. Return format is: + '(TYPE DEPTHNUMBER) +where TYPE is one of (comment, code, function, blockstart, blockmid, +blockendless, blockend) DEPTHNUMBER is how many characters to indent +this line." + (unless lvl2 (setq lvl2 (matlab-compute-line-context 2))) + + (let ((lvl1 (matlab-get-lvl1-from-lvl2 lvl2)) (tmp nil)) (cond ;; COMMENTS @@ -1931,7 +1925,7 @@ LVL2 is a level 2 scan context with info from previous lines." ;; BLOCK START is like regular comment ((eq comment-style 'block-start) ;; indent like code, but some users like anti-indent - (list 'comment (+ ci matlab-comment-anti-indent)) + (list 'comment (+ (matlab--previous-line-indent-recommendation lvl2) matlab-comment-anti-indent)) ) ;; COMMENT REGION comments ((matlab-line-comment-ignore-p lvl1) @@ -1940,24 +1934,27 @@ LVL2 is a level 2 scan context with info from previous lines." ((setq tmp (matlab-line-continued-comment)) ;;; TODO : REPLACE (list 'comment tmp)) (t - (list 'comment (+ ci matlab-comment-anti-indent)))))) + (list 'comment (+ (matlab--previous-line-indent-recommendation lvl2) matlab-comment-anti-indent)))))) ;; FUNCTION DEFINITION ((matlab-line-declaration-p lvl1) - (if matlab-functions-have-end - ;; A function line has intrinsic indentation iff function bodies are - ;; not indented and the function line is nested within another function. - (if (and (not (matlab-indent-function-body-p)) - (save-excursion - (beginning-of-line) - ;; TODO - maybe replace this? Not usually used. - (matlab--scan-block-backward-up-until 'decl))) - (setq ci (+ ci matlab-indent-level)) - ;; If no intrinsic indentation, do not change from ci. + (cond ((not matlab-functions-have-end) + (setq tmp 0)) + ;; If we do have ends, check if we are nexted + ;; A function line has intrinsic indentation iff function bodies are + ;; not indented and the function line is nested within another function. + ((and (not (matlab-indent-function-body-p)) + (save-excursion + (beginning-of-line) + ;; TODO - maybe replace this? Not usually used. + (matlab--scan-block-backward-up-until 'decl))) + (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) matlab-indent-level))) + (t + ;; If no intrinsic indentation, do not change from current-indentation. + (setq tmp (matlab--previous-line-indent-recommendation lvl2))) ) - ;; If functions are not nested, functions go to left margin. - (setq ci 0)) - (list 'function ci)) + (list 'function tmp)) + ;; END keyword ((matlab-line-end-p lvl1) (let* ((CTXT (matlab-with-context-line lvl1 @@ -1968,17 +1965,21 @@ LVL2 is a level 2 scan context with info from previous lines." (if (matlab--maybe-yes-or-no-p matlab-functions-have-end-should-be-true t) (matlab-functions-have-end-minor-mode 1) (error "Unmatched end"))) + (if (matlab-indent-function-body-p) - ;;(setq ci (- ci matlab-indent-level)) - ;; match indentation of the function regardless of any other + ;; Match indentation of the function regardless of any other ;; state that might have gotten messed up. - (setq ci (matlab-line-indentation (nth 3 CTXT))) + (setq tmp (matlab-line-indentation (nth 3 CTXT))) + + ;; Else, no change + (setq tmp (matlab--previous-line-indent-recommendation lvl2)) )) - ;; Next, see if this line starts with an end, and whether the - ;; end is matched, and whether the line is blank up to the match. - ;; If so, return the indentation of the match. - (setq ci (matlab-line-indentation (nth 3 CTXT))))) - (list 'blockend ci)) + + ;; Not a declaration. In that case, just match up with the + ;; line that the block stat is on. + (setq tmp (matlab-line-indentation (nth 3 CTXT))))) + (list 'blockend tmp)) + ;; ELSE/CATCH keywords ((matlab-line-block-middle-p lvl1) (list 'blockmid @@ -1989,6 +1990,7 @@ LVL2 is a level 2 scan context with info from previous lines." (if (not (eq (matlab-on-keyword-p) 'ctrl)) (error "Does not match opening block type")) (current-column))))) + ;; CASE/OTHERWISE keywords ((matlab-line-block-case-p lvl1) (list 'blockendless @@ -2002,6 +2004,7 @@ LVL2 is a level 2 scan context with info from previous lines." (if (listp matlab-case-indent-level) (car matlab-case-indent-level) matlab-case-indent-level))))))) + ;; END of a MATRIX ((matlab-line-close-paren-p lvl1) (list 'array-end (let* ((fc (matlab-line-close-paren-inner-char lvl1)) @@ -2014,14 +2017,15 @@ LVL2 is a level 2 scan context with info from previous lines." (cdr (cdr mi)) (cdr mi)) nil))) ;; apply the maximum limits. - (if (and ind (> (- pc ci) max)) + (if (and ind (> (- pc (matlab--previous-line-indent-recommendation lvl2)) max)) (1- ind) ; decor pc)))) + ;; CODE LINES ((and (not (matlab-line-close-paren-outer-point lvl1)) (not (matlab-scan-previous-line-ellipsis-p))) ;; Code always matches up against the previous line. - (list 'code ci)) + (list 'code (matlab--previous-line-indent-recommendation lvl2))) ;; CONTINUATION : A group of cases for continuation (t @@ -2052,105 +2056,107 @@ LVL2 is a level 2 scan context with info from previous lines." (found-column nil) ) - (list indent-type - (save-excursion - (cond - ((and - ;; CONTINUATION with FUNCTIONs that indent past arg1 - (eq indent-type 'function-call-cont) - ;; This checks for our special set of functions. - (save-excursion - (goto-char parenpt) - (forward-symbol -1) - (looking-at - matlab-indent-past-arg1-functions)) - ;; We are in a fcn call, AND the fcn wants to - ;; indent past the first argument. Only do so - ;; if first arg is a SIMPLE EXPR. - (matlab-navigation-syntax - (goto-char parenpt) - (looking-at "(\\s-*\\w+\\s-*,") - (setq found-column (match-end 0))) - (save-excursion - (goto-char found-column) ; move to comma - ;; Don't bother if we hit the EOL. - (not (looking-at "\\s-*\\(\\.\\.\\.\\|$\\|)\\)")))) - ;; We are in the right kind of place. Lets - ;; start indenting - (goto-char found-column) - (skip-chars-forward " \t") - (if (> (- (current-column) parencol) - matlab-arg1-max-indent-length) - (+ parencol matlab-arg1-max-indent-length) - (current-column))) + (save-excursion + (cond + ((and + ;; CONTINUATION with FUNCTIONs that indent past arg1 + (eq indent-type 'function-call-cont) + ;; This checks for our special set of functions. + (save-excursion + (goto-char parenpt) + (forward-symbol -1) + (looking-at + matlab-indent-past-arg1-functions)) + ;; We are in a fcn call, AND the fcn wants to + ;; indent past the first argument. Only do so + ;; if first arg is a SIMPLE EXPR. + (matlab-navigation-syntax + (goto-char parenpt) + (looking-at "(\\s-*\\w+\\s-*,") + (setq found-column (match-end 0))) + (save-excursion + (goto-char found-column) ; move to comma + ;; Don't bother if we hit the EOL. + (not (looking-at "\\s-*\\(\\.\\.\\.\\|$\\|)\\)")))) + ;; We are in the right kind of place. Lets + ;; start indenting + (goto-char found-column) + (skip-chars-forward " \t") + (if (> (- (current-column) parencol) + matlab-arg1-max-indent-length) + (setq tmp (+ parencol matlab-arg1-max-indent-length)) + (setq tmp (current-column)))) - ;; CONTINUATION with PARENS - (parenchar ;; a str if in parens - (let* ((mi (assoc parenchar matlab-maximum-indents)) - (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) nil)) - (ind (if mi (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)) nil))) - (goto-char parenpt) - (forward-char 1) - (skip-chars-forward " \t") - ;; If we are at the end of a line and this - ;; open paren is there, then we DON'T want - ;; to indent to it. Use the standard - ;; indent. - (if (or (not matlab-align-to-paren) - (looking-at "\\.\\.\\.\\|$")) - (if (or (eq indent-type 'function-call-cont) - (and (not (eq indent-type 'array-solo-cont)) - (not (eq indent-type 'nested-array-cont)))) - ;; functions or an array ending on a EOL should - ;; do normal code indentation from beginning of cmd - (+ ci-boc matlab-continuation-indent-level) - ;; If in an array in an array ending on EOL should - ;; indent a wee bit - (+ parencol matlab-array-continuation-indent-level)) - ;; current column is location on original line where - ;; first bit of text is, so line up with that. - (current-column) - ;; TODO - this disables indentation MAXs - ;; if we really want to be rid of this - ;; we can dump a bunch of logic above too. - ;; apply the maximum limits. - ;;(if (and ind (> (- (current-column) ci-boc) max)) - ;; (+ ci-boc ind) - ;; (current-column)) - ))) + ;; CONTINUATION with PARENS + (parenchar ;; a str if in parens + (let* ((mi (assoc parenchar matlab-maximum-indents)) + (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) nil)) + (ind (if mi (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)) nil))) + (goto-char parenpt) + (forward-char 1) + (skip-chars-forward " \t") + ;; If we are at the end of a line and this + ;; open paren is there, then we DON'T want + ;; to indent to it. Use the standard + ;; indent. + (if (or (not matlab-align-to-paren) + (looking-at "\\.\\.\\.\\|$")) + (if (or (eq indent-type 'function-call-cont) + (and (not (eq indent-type 'array-solo-cont)) + (not (eq indent-type 'nested-array-cont)))) + ;; functions or an array ending on a EOL should + ;; do normal code indentation from beginning of cmd + (setq tmp (+ ci-boc matlab-continuation-indent-level)) + ;; If in an array in an array ending on EOL should + ;; indent a wee bit + (setq tmp (+ parencol matlab-array-continuation-indent-level))) + ;; current column is location on original line where + ;; first bit of text is, so line up with that. + (setq tmp (current-column)) + ;; TODO - this disables indentation MAXs + ;; if we really want to be rid of this + ;; we can dump a bunch of logic above too. + ;; apply the maximum limits. + ;;(if (and ind (> (- (current-column) ci-boc) max)) + ;; (+ ci-boc ind) + ;; (current-column)) + ))) - ;; CONTINUATION with EQUALS - ((save-excursion - (goto-char boc) - (while (and (re-search-forward "=" (matlab-point-at-eol) t) - (matlab-cursor-in-string-or-comment))) - (when (= (preceding-char) ?=) - (skip-chars-forward " \t") - (setq found-column (point))) - ) - (save-excursion - (goto-char found-column) - (let ((cc (current-column)) - (mi (assoc ?= matlab-maximum-indents))) + ;; CONTINUATION with EQUALS + ((save-excursion + (goto-char boc) + (while (and (re-search-forward "=" (matlab-point-at-eol) t) + (matlab-cursor-in-string-or-comment))) + (when (= (preceding-char) ?=) + (skip-chars-forward " \t") + (setq found-column (point))) + ) + (save-excursion + (goto-char found-column) + (let ((cc (current-column)) + (mi (assoc ?= matlab-maximum-indents))) - (if (looking-at "\\.\\.\\.\\|$") - ;; In this case, the user obviously wants the - ;; indentation to be somewhere else. - (+ ci (cdr (cdr mi))) - ;; If the indent delta is greater than the max, - ;; use the max + current - (if (and mi (> (- cc ci) (if (listp (cdr mi)) - (car (cdr mi)) - (cdr mi)))) - (setq cc (+ ci (if (listp (cdr mi)) - (cdr (cdr mi)) + (if (looking-at "\\.\\.\\.\\|$") + ;; In this case, the user obviously wants the + ;; indentation to be somewhere else. + (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) (cdr (cdr mi)))) + ;; If the indent delta is greater than the max, + ;; use the max + current + (if (and mi (> (- cc (matlab--previous-line-indent-recommendation lvl2)) (if (listp (cdr mi)) + (car (cdr mi)) (cdr mi)))) - cc))))) + (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) (if (listp (cdr mi)) + (cdr (cdr mi)) + (cdr mi)))) + (setq tmp cc)))))) - ;; CONTINUATION with nothing special about it. - (t - (+ ci matlab-continuation-indent-level)) - ))))) + ;; CONTINUATION with nothing special about it. + (t + (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) matlab-continuation-indent-level))) + )) + + (list indent-type tmp) + )) ))) (defun matlab-next-line-indentation (lvl1) From 2435bf1000a2d5424ef0a0c8d4353adbd5c078e4 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 12:46:34 -0400 Subject: [PATCH 123/168] matlab-syntax.el: (matlab-syntax-commanddual-functions): Add cd and colormap. (matlab--scan-line-for-command-dual): Make sure case-fold-search set so we don't match cmd dual if user messes w/ capitalization. (matlab--font-lock-syntactic-face): Add support for ignored comment face. (matlab-cursor-comment-string-context): Improve help. matlab.el: (matlab-ignored-comment-face): New tests/metest.el: (metest-comment-string-syntax-test): Add ignored comment support. Add 'regular' code support (ie - not string or comment) (met-kw-font-alist): Add ignored comment support. tests/fontlock.m: Add tests for ignored comment face. tests/stringtest.m: Add ignored cmd dual cases. Add ignored comment detection and tests. --- matlab-syntax.el | 19 ++++++++++++------- matlab.el | 7 +++++++ tests/fontlock.m | 5 ++++- tests/metest.el | 12 +++++++++++- tests/stringtest.m | 13 ++++++++++++- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 96237f2..aa87951 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -209,11 +209,11 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ))) (defconst matlab-syntax-commanddual-functions - '("warning" "disp" + '("warning" "disp" "cd" ;; debug "dbstop" "dbclear" ;; Graphics - "print" "xlim" "ylim" "zlim" "grid" "hold" "box") + "print" "xlim" "ylim" "zlim" "grid" "hold" "box" "colormap") "Functions that are commonly used with commandline dual") (defconst matlab-cds-regex (regexp-opt matlab-syntax-commanddual-functions 'symbols)) @@ -221,11 +221,12 @@ and `matlab--scan-line-for-unterminated-string' for specific details." "Scan this line for command line duality strings." ;; Note - add \s$ b/c we'll add that syntax to the first letter, and it ;; might still be there during an edit! - (when (looking-at - (concat "^\\s-*" - matlab-cds-regex - "\\s-+\\(\\s$\\|\\w\\|\\s_\\)")) - (goto-char (match-beginning 2)))) + (let ((case-fold-search nil)) + (when (looking-at + (concat "^\\s-*" + matlab-cds-regex + "\\s-+\\(\\s$\\|\\w\\|\\s_\\)")) + (goto-char (match-beginning 2))))) (matlab--syntax-symbol matlab--transpose-syntax '(3 . nil) ;; 3 = symbol "Treat ' as non-string when used as transpose.") @@ -328,6 +329,9 @@ Called when comments found in `matlab--scan-line-for-unterminated-string'." ((and (< (nth 8 pps) (point-max)) (= (char-after (1+ (nth 8 pps))) ?\#)) 'matlab-pragma-face) + ((and (< (nth 8 pps) (point-max)) + (looking-at "\\^\\| \\$\\$\\$")) + 'matlab-ignored-comment-face) (t 'font-lock-comment-face)) )) @@ -394,6 +398,7 @@ Return 'comment if in a comment. Return 'string if in a string. Return 'charvector if in a character vector Return 'ellipsis if after an ... ellipsis +Return 'commanddual if in text interpreted as string for command dual Return nil if none of the above. Scans from the beginning of line to determine the context. If optional BOUNDS-SYM is specified, set that symbol value to the diff --git a/matlab.el b/matlab.el index a746989..0e97bfa 100644 --- a/matlab.el +++ b/matlab.el @@ -701,6 +701,13 @@ point, but it will be restored for them." :bold t)) "*Face to use for cellbreak %% lines.") +(defface matlab-ignored-comment-face + '((t :inherit font-lock-comment-face + :height .8 + :slant italic)) + "*Face to use for ignored comments. +Ignored comments are lines that start with '% $$$' or '%^'.") + (defface matlab-pragma-face '((t :inherit font-lock-comment-face :bold t)) diff --git a/tests/fontlock.m b/tests/fontlock.m index a2433df..8319118 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -2,8 +2,11 @@ % TEST FILE FOR FONT LOCK SPECIAL WORDS function fontlock() %^ ^kw ^fn ^df -%^ ^co +%^ ^ig +% $$$ ignored comment +%^ ^ig + persistent var1 % !!4 %^ ^kw ^vn ^co diff --git a/tests/metest.el b/tests/metest.el index aa7a9fb..a544844 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -160,7 +160,7 @@ ;;(message ">> Starting string/comment detect loop in %S" (current-buffer)) - (while (re-search-forward "#\\([cCsSvVebd]\\)#" nil t) + (while (re-search-forward "#\\([cCisSvVebdr]\\)#" nil t) (let* ((md (match-data)) (pt (match-end 1)) (mc (match-string-no-properties 1)) @@ -178,8 +178,10 @@ (and (string= "S" mc) (eq 'string qd)) (and (string= "c" mc) (eq 'comment qd)) (and (string= "C" mc) (eq 'comment qd)) + (and (string= "i" mc) (eq 'comment qd)) (and (string= "e" mc) (eq 'ellipsis qd)) (and (string= "d" mc) (eq 'commanddual qd)) + (and (string= "r" mc) (eq nil qd)) ) (metest-error "Syntax Test Failure @ char %d: Expected %s but found %S" pt @@ -190,8 +192,10 @@ ((string= mc "S") "string") ((string= mc "c") "comment") ((string= mc "C") "comment") + ((string= mc "i") "comment") ((string= mc "e") "ellipsis") ((string= mc "d") "commanddual") + ((string= mc "r") "normal code") (t "unknown test token")) qd)) ;; Test 2 - is match-data unchanged? @@ -207,8 +211,10 @@ (and (string= "S" mc) (eq fnt 'matlab-unterminated-string-face)) (and (string= "c" mc) (eq fnt 'font-lock-comment-face)) (and (string= "C" mc) (eq fnt 'matlab-cellbreak-face)) + (and (string= "i" mc) (eq fnt 'matlab-ignored-comment-face)) (and (string= "e" mc) (eq fnt 'font-lock-comment-face)) (and (string= "d" mc) (eq fnt 'matlab-commanddual-string-face)) + (and (string= "r" mc) (eq fnt nil)) ) (metest-error "Font Lock Failure @ char %d: Expected %s but found %S" pt @@ -218,8 +224,11 @@ ((string= mc "s") "string face") ((string= mc "S") "unterminated string face") ((string= mc "c") "comment face") + ((string= mc "C") "cellbreak face") + ((string= mc "i") "ignored comment face") ((string= mc "e") "comment face") ((string= mc "d") "commanddual string face") + ((string= mc "r") "regular code / no face") (t "unknown test token")) (get-text-property pt 'face))) ;; Track @@ -414,6 +423,7 @@ ( "cn" . font-lock-constant-face ) ( "co" . font-lock-comment-face ) ( "cb" . matlab-cellbreak-face ) + ( "ig" . matlab-ignored-comment-face ) ( "pr" . matlab-pragma-face ) ( "cd" . matlab-commanddual-string-face ) ( "st" . font-lock-string-face ) diff --git a/tests/stringtest.m b/tests/stringtest.m index bc7812b..2d57ce1 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -1,4 +1,3 @@ -% -*- matlab-show-mlint-warnings: nil -*- %% Tests for char vector and string handling. % % #c# @@ -166,6 +165,12 @@ not_commented(); % Normal comment #c# +%% Ignored Comments #C# + +% $$$ This comment is ignored by indentation engine. #i# + +%^ This comment is igored too, used in tests for font lock. #i# + %% Command line dual #C# % Note: stuff after a symbol treated as string @@ -173,9 +178,15 @@ not_commented(); disp _this is string input to function #d#_ disp _this is also string input to a function #d#_ +regularcode; #r# + +% Note: Case sensitivity of cmd dual functions +DISP _regular code even though ML would treat as cmd dual #r#_ + %{ % Local Variables: % matlab-syntax-support-command-dual: t +% matlab-show-mlint-warnings: nil % End: %} From 373023d062ce2d156390dba9250645e6fdfbdbb4 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 12:57:36 -0400 Subject: [PATCH 124/168] matlab-syntax.el: (matlab--scan-line-for-command-dual): Don't do cmd dual checks if inside a list. --- matlab-syntax.el | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index aa87951..d97195c 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -222,11 +222,12 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ;; Note - add \s$ b/c we'll add that syntax to the first letter, and it ;; might still be there during an edit! (let ((case-fold-search nil)) - (when (looking-at - (concat "^\\s-*" - matlab-cds-regex - "\\s-+\\(\\s$\\|\\w\\|\\s_\\)")) - (goto-char (match-beginning 2))))) + (when (and (not (nth 9 (syntax-ppss (point)))) + (looking-at + (concat "^\\s-*" + matlab-cds-regex + "\\s-+\\(\\s$\\|\\w\\|\\s_\\)")) + (goto-char (match-beginning 2)))))) (matlab--syntax-symbol matlab--transpose-syntax '(3 . nil) ;; 3 = symbol "Treat ' as non-string when used as transpose.") From c039c7369f65e449978857671c304ab16b9a38a7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 15:09:22 -0400 Subject: [PATCH 125/168] matlab-syntax.el: (matlab--scan-line-for-command-dual): Fix typo in logic to pull goto-char out of and construct. --- matlab-syntax.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index d97195c..d099f92 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -226,8 +226,8 @@ and `matlab--scan-line-for-unterminated-string' for specific details." (looking-at (concat "^\\s-*" matlab-cds-regex - "\\s-+\\(\\s$\\|\\w\\|\\s_\\)")) - (goto-char (match-beginning 2)))))) + "\\s-+\\(\\s$\\|\\w\\|\\s_\\)"))) + (goto-char (match-beginning 2))))) (matlab--syntax-symbol matlab--transpose-syntax '(3 . nil) ;; 3 = symbol "Treat ' as non-string when used as transpose.") From f9df26d3f2af1fe9c470010037ea15df51f8aebe Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 18:43:13 -0400 Subject: [PATCH 126/168] matlab-maint.el: (matlab-maint-run-tests): Save current buffer w/out asking, as that is typically what I want. --- matlab-maint.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matlab-maint.el b/matlab-maint.el index eb93015..ee6b071 100644 --- a/matlab-maint.el +++ b/matlab-maint.el @@ -148,6 +148,8 @@ "Run the tests for matlab mode. With universal ARG, ask for the code to be run with output tracking turned on." (interactive "P") + (when (buffer-file-name) + (save-buffer)) (save-excursion (matlab-maint-set-buffer-to "tests/Makefile") (if (or arg matlab-shell-io-testing) From 882cbd385f709c0539f5861a403cd72f74cae8ee Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 18:44:47 -0400 Subject: [PATCH 127/168] matlab-scan.el: (matlab--valid-keyword-node): input 'node' is now optional. (matlab--scan-next-keyword): Fix typo for 'args input extra filter. If any old keyword requested, use generic valid keyword filter. --- matlab-scan.el | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 5bb2141..9c45cf6 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -893,16 +893,17 @@ Returns nil if preceeding non-whitespace char is `.'" nil parent)) -(defun matlab--valid-keyword-node (node &optional parentblock) +(defun matlab--valid-keyword-node (&optional node parentblock) "Return non-nil if NODE is in a valid location. Optional parentblock specifies containing parent block if it is known." + (when (not node) (setq node (matlab--mk-keyword-node))) (when node (save-excursion (goto-char (nth 2 node)) (and (matlab--valid-keyword-point) (cond ((eq (car node) 'mcos) (matlab--valid-mcos-keyword-point parentblock)) - ((eq (car node) 'arg) + ((eq (car node) 'args) (matlab--valid-arguments-keyword-point parentblock)) (t t)))))) @@ -979,7 +980,9 @@ If nothing is found before LIMIT, then stop and return nil." #'matlab--valid-mcos-keyword-point) ((eq keyword-type 'args) #'matlab--valid-arguments-keyword-point) - (t nil)))) + (t + ;; Not sure - do the super check + #'matlab--valid-keyword-node)))) (matlab-re-search-keyword-forward regex limit t filter))) ;;; Block Scanning From af5f6c156d75db7e78b7a0be62392828d912d2b1 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 18:45:23 -0400 Subject: [PATCH 128/168] matlab-syntax.el: (matlab-syntax-commanddual-functions): add 'axis' command. --- matlab-syntax.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index d099f92..4663984 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -213,7 +213,7 @@ and `matlab--scan-line-for-unterminated-string' for specific details." ;; debug "dbstop" "dbclear" ;; Graphics - "print" "xlim" "ylim" "zlim" "grid" "hold" "box" "colormap") + "print" "xlim" "ylim" "zlim" "grid" "hold" "box" "colormap" "axis") "Functions that are commonly used with commandline dual") (defconst matlab-cds-regex (regexp-opt matlab-syntax-commanddual-functions 'symbols)) From 4c389c08fa92d77b2889dd3ea1e3cc7c1115b9c4 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 18:52:45 -0400 Subject: [PATCH 129/168] matlab.el: (matlab-math-face): New (matlab-simulink-keyword-face): Inherit from builtin, not type. (matlab-ignored-comment-face): Remove height change, too disruptive. (matlab-keyword-list, matlab-keyword-first-on-line-list): DELETED (now in matlab-scan.el) (matlab-hg-primitives-list): New name, many new words. (matlab-constants-keyword-list): Add flintmax. (matlab-font-lock-regexp-opt): Use symbol ends. (matlab-font-lock-mcos-keyword-match): After match, skip over attribute list. Needed for anchored matchers. (matlab-font-lock-anchor-set-end-limit): Don't change match data. (matlab-font-lock-anchor-variable-match): Update regex to be more robust. Always move endpoint to EOL. (matlab-basic-font-lock-keywords): Use symbol end matchers \\_< and \\_> where needed. Delete crufty old comments. (matlab-really-gaudy-font-lock-keywords): Add more logical things and use built-in face on them. tests/metest.el: Add new instrumentation options for new faces used by font lockers. tests/blocks.m, tests/mclass.m: Fixup for changes in font lock faces used. --- matlab.el | 132 ++++++++++++++++++++++++----------------------- tests/blocks.m | 6 +-- tests/fontlock.m | 25 +++++---- tests/mclass.m | 4 +- tests/metest.el | 7 ++- 5 files changed, 93 insertions(+), 81 deletions(-) diff --git a/matlab.el b/matlab.el index 0e97bfa..471e037 100644 --- a/matlab.el +++ b/matlab.el @@ -666,6 +666,9 @@ point, but it will be restored for them." (defvar matlab-cellbreak-face 'matlab-cellbreak-face "Self reference for cellbreaks.") +(defvar matlab-math-face 'matlab-math-face + "Self reference for math.") + (defface matlab-unterminated-string-face '((t :inherit font-lock-string-face :underline t)) @@ -679,7 +682,7 @@ point, but it will be restored for them." :group 'matlab) (defface matlab-simulink-keyword-face - '((t :inherit font-lock-type-face + '((t :inherit font-lock-builtin-face :underline t)) "*Face used to highlight simulink specific functions." :group 'matlab) @@ -703,7 +706,6 @@ point, but it will be restored for them." (defface matlab-ignored-comment-face '((t :inherit font-lock-comment-face - :height .8 :slant italic)) "*Face to use for ignored comments. Ignored comments are lines that start with '% $$$' or '%^'.") @@ -713,6 +715,11 @@ Ignored comments are lines that start with '% $$$' or '%^'.") :bold t)) "*Face to use for cellbreak %% lines.") +(defface matlab-math-face + '((t :inherit font-lock-constant-face + :slant italic)) + "*Face to use for cellbreak %% lines.") + ;;; Font Lock MLINT data highlighting ;; TODO - THE BELOW LOOKS BROKEN TO ME. @@ -770,32 +777,39 @@ Argument LIMIT is the maximum distance to search." nil ;; no matches, stop ))) -(defcustom matlab-keyword-list '("global" "persistent" "for" "parfor" "while" - "spmd" "if" "elseif" "else" - "return" "break" "continue" - "switch" "case" "otherwise" "try" - "catch" "tic" "toc" - ) - "List of keywords for MATLAB used in highlighting. -Customizing this variable is only useful if `regexp-opt' is available." - :group 'matlab - :type '(repeat (string :tag "Keyword: "))) -(defcustom matlab-keyword-first-on-line-list '( "properties" "methods" "enumeration" "events" - "arguments" - ) - "List of keywords for MATLAB that should be highilghted only if the first word on a line. -Customizing this variable is only useful if `regexp-opt' is available." - :group 'matlab - :type '(repeat (string :tag "Keyword: "))) - -(defcustom matlab-handle-graphics-list '("figure" "axes" "axis" "line" - "surface" "patch" "text" "light" - "image" "set" "get" "uicontrol" - "uimenu" "uitoolbar" - "uitoggletool" "uipushtool" - "uicontext" "uicontextmenu" - "setfont" "setcolor") +(defcustom matlab-hg-primitives-list + '(;; start with basic / primitive objects + "figure" "axes" "line" "surface" "patch" "text" "light" "image" "imagesc" + "rectangle" "animatedline" + ;; core utilities + "set" "get" "reset" "copyobj" "findobj" "cla" "clf" "shg" + ;; popular helpers + "axis" "hold" "title" "xlabel" "ylabel" "zlabel" "xlim" "ylim" "zlim" "rlim" "thetalim" + "lighting" "shading" "material" + ;; popular cartesian charts + "plot" "plot3" "semilogx" "semilogy" "loglog" "scatter" "scatter3" "stackedplot" + "area" "errorbar" "bubblechart" "bubblechart3" "swarmchart" "swarmchart3" "spy" + "histogram" "histogram2" "wordcloud" "bubblecloud" "heatmap" "parallelplot" + "bar" "barh" "bar3" "bar3h" "stem" "stair" "quiver" "quiver3" "stem3" + "contour" "contourf" "contour3" "contourslice" "fcontour" + ;; 3D + "surf" "surfc" "surfl" "ribbon" "pcolor" "mesh" "meshc" "meshz" "waterfall" + ;; anim + "comet" "comet3" + ;; polar + "polarplot" "polarscatter" "polarhistogram" "polarbubblechart" + ;; geographic + "goeplot" "geoscatter" "geobubble" "geodensity" + ;; function plots + "fplot" "fplot3" "fimplicit" "fsurf" "fimplicit3" + ;; misc tools + "legend" "colorbar" "tiledlayout" "nexttile" "subplot" "annotation" + ;; Componnents + "uicontrol" "uimenu" "uitoolbar" "uitoggletool" "uipushtool" "uicontext" "uicontextmenu" + ;; misc dialogs + "uisetfont" "uisetcolor" "uigetfile" "uiputfile") + "List of handle graphics functions used in highlighting. Customizing this variable is only useful if `regexp-opt' is available." :group 'matlab @@ -822,7 +836,7 @@ Customizing this variable is only useful if `regexp-opt' is available." (defcustom matlab-constants-keyword-list - '("eps" "pi" "inf" "Inf" "nan" "NaN" "ans" "i" "j" "NaT" "true" "false") + '("eps" "pi" "flintmax" "inf" "Inf" "nan" "NaN" "ans" "i" "j" "NaT" "true" "false") "List of constants and special variables in MATLAB." :group 'matlab :type '(repeat (string :tag "Debug Keyword: "))) @@ -830,11 +844,11 @@ Customizing this variable is only useful if `regexp-opt' is available." (defun matlab-font-lock-regexp-opt (keywordlist) "Create a font-lock usable KEYWORDLIST matching regular expression. Uses `regex-opt' if available. Otherwise creates a 'dumb' expression." - (concat "\\<\\(" + (concat "\\_<\\(" (if (fboundp 'regexp-opt) (regexp-opt keywordlist) (mapconcat (lambda (s) s) keywordlist "\\|")) - "\\)\\>")) + "\\)\\_>")) ;;; Font lock keyword handlers ;; @@ -852,9 +866,13 @@ Fails to match when keywords show up as variables, etc." (defun matlab-font-lock-mcos-keyword-match (limit) "Font lock matcher for mcos keywords. Fails to match when keywords show up as variables, etc." - (and (eq matlab-functions-have-end 'class) - (setq matlab-fl-anchor-keyword - (matlab--scan-next-keyword 'mcos limit)))) + (when (and (eq matlab-functions-have-end 'class) + (setq matlab-fl-anchor-keyword + (matlab--scan-next-keyword 'mcos limit))) + (save-match-data + ;; Skip over attributes. + (when (looking-at "\\s-*(") (forward-sexp 1))) + t)) (defun matlab-font-lock-args-keyword-match (limit) "Font lock matcher for mcos keywords. @@ -890,11 +908,12 @@ color support." (defun matlab-font-lock-anchor-set-end-limit () "Set the end limit for anchored matchers." (save-excursion - ;; next keyword is faster, plus if someone is in the middle of typing - ;; a new block, prevents going too far into the distance. - (matlab--scan-next-keyword 'all (point-max)) - (forward-word -1) - (setq ml-fl-anchor-limit (point)))) + (save-match-data + ;; next keyword is faster, plus if someone is in the middle of typing + ;; a new block, prevents going too far into the distance. + (matlab--scan-next-keyword 'all (point-max)) + (forward-word -1) + (setq ml-fl-anchor-limit (point))))) (defun matlab-font-lock-anchor-clear-end-limit () "Clear the end limit for anchored matchers." @@ -905,18 +924,18 @@ color support." This matcher will handle a range of variable features." (when (member (nth 1 matlab-fl-anchor-keyword) '("properties" "events" "arguments")) - (let* ((match (re-search-forward "^\\s-+\\(\\w+\\)\\_>" ml-fl-anchor-limit t)) + (let* ((match (re-search-forward "\\(?:^\\|[,;]\\)\\s-+\\(\\w+\\)\\_>" ml-fl-anchor-limit t)) ;; Save this match so we can do a 2nd anchored search for a data type. (md1 (list (match-beginning 1) (match-end 1))) (tm (looking-at - "\\s-*\\(\\(?:([^\n)]+)\\)?\\s-*\\(?:\\w+\\|\\.\\)*\\)\\s-*\\($\\|[.%{=]\\)")) + "\\(\\(?:\\s-*([^\n\)]+)\\s-*\\|\\s-+\\)?\\(?:\\w+\\|\\.\\)*\\)\\s-*\\($\\|[.%{=]\\)")) (tm1 (if tm (list (match-beginning 1) (match-end 1)) ;; The below is a cheat to not highlight anything but ;; still supply the match data for this optional piece. (list (nth 1 md1) (nth 1 md1)))) (newmdata (append md1 md1 tm1))) (when match - (goto-char (apply 'max newmdata)) + (goto-char (point-at-eol)) (set-match-data newmdata) t)))) @@ -937,12 +956,12 @@ This matcher will handle a range of variable features." (0 font-lock-keyword-face)) ;; Handle graphics stuff (list - (matlab-font-lock-regexp-opt matlab-handle-graphics-list) - '(0 font-lock-type-face)) + (matlab-font-lock-regexp-opt matlab-hg-primitives-list) + '(0 font-lock-builtin-face)) (list ;; How about a few matlab constants such as pi, infinity, and sqrt(-1)? (matlab-font-lock-regexp-opt matlab-constants-keyword-list) - 1 font-lock-constant-face) + 1 'matlab-math-face) ;; Imaginary number support '("\\<[0-9]\\.?\\(i\\|j\\)\\_>" 1 font-lock-reference-face) ) @@ -1021,7 +1040,7 @@ This matcher will handle a range of variable features." '("\\_<\\(case\\|switch\\)\\_>\\s-+\\({[^}\n]+}\\|[^,%\n]+\\)" (2 font-lock-reference-face)) ;; set_param and waitfor have input variables that can be highlighted. - (list (concat "\\<" matlab-indent-past-arg1-functions "\\s-*") + (list (concat "\\_<" matlab-indent-past-arg1-functions "\\_>\\s-*") '("(\\s-*\\(\\w+\\)\\s-*\\(,\\|)\\)" nil nil (1 font-lock-variable-name-face))) ) @@ -1060,17 +1079,6 @@ This matcher will handle a range of variable features." (2 font-lock-type-face t) )) - ;; Properties can have a type syntax after them - ;;'("^\\s-*\\w+\\s-*\\(([:0-9,]+)\\s-*[^{=\n]+\\)" - ;; (1 font-lock-type-face nil nil)) - ;; Properties blocks are full of variables - ;;'("^\\s-*\\(properties\\|events\\|arguments\\)\\s-*[(,;%\n]" - ;; ("^\\s-*\\(\\sw+\\)\\>" ;; This part matches the variable - ;; ;; extend region to match in - ;; (save-excursion (matlab-forward-sexp t) (beginning-of-line) (point)) - ;; nil - ;; (1 font-lock-variable-name-face t)) - ;; ) ) "List of font-lock keywords used when an MATLAB file contains a class.") @@ -1087,8 +1095,8 @@ This matcher will handle a range of variable features." (append (list ;; Since it's a math language, how bout dem symbols? - '("\\([<>~]=?\\|\\.[/*^']\\|==\\|\\\\|[-!^&|*+\\/~:]\\)" - 1 font-lock-type-face) + '("\\([<>~=]=\\|\\.[/\\*^'?]\\|\\_<\\(?:\\\\|[-<>!?^&|*+\\/~:@]\\)" + 1 font-lock-builtin-face) '("[]A-Za-z0-9_\"})']\\('+\\)" 1 font-lock-type-face) ;; How about references in the HELP text. (list (concat "^" matlab-comment-line-s "\\s-*" @@ -1107,13 +1115,7 @@ This matcher will handle a range of variable features." '(0 'bold)) ;; Simulink functions (list (matlab-font-lock-regexp-opt matlab-simulink-keywords) - ;;(list (list (concat "\\<\\(\\([sg]et_param\\|sim\\([gs]et\\)?\\|" - ;; "\\(mld\\|ss\\)[A-Z]\\w+\\)\\|" - ;; "\\(new\\|open\\|close\\|save\\|find\\)_system\\|" - ;; "\\(add\\|delete\\|replace\\)_\\(block\\|line\\)\\|" - ;; "simulink\\|bd\\(root\\|close\\)" - ;; "\\)\\>") - 1 matlab-simulink-keyword-face) + 1 matlab-simulink-keyword-face) )) "Expressions to highlight in MATLAB mode.") diff --git a/tests/blocks.m b/tests/blocks.m index 1d6de3b..045cc2c 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -14,7 +14,7 @@ classdef blocks < handle %properties = 1; %methods = 1; %events = 1; - arguments %!!8 + arguments %!!8 prop = 1; end %<<12 @@ -78,7 +78,7 @@ classdef blocks < handle %>>20 function events=arguments(arguments)%!!8 arguments, arguments(:,:) {mustBeNumeric}, end %!!12 -%^ ^kw ^df ^ty ^df ^kw ^co +%^ ^kw ^vn ^ty ^df ^kw ^co enumeration ... %!!12 ...%^ ^df = arguments; @@ -86,7 +86,7 @@ classdef blocks < handle if enumeration > 0 %!!12 arguments = -enumeration; %!!16 -%^ ^df ^ty ^df +%^ ^df ^bi ^df end %!!12 events ... %!!12 diff --git a/tests/fontlock.m b/tests/fontlock.m index 8319118..626caa9 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -50,7 +50,7 @@ end function keywordstuff() while true -%^ ^kw ^cn +%^ ^kw ^ma for varname=1:10 %^ ^kw ^vn ^cn @@ -59,10 +59,10 @@ function keywordstuff() end if varname == 2 -%^ ^kw ^df ^ty ^df +%^ ^kw ^df ^bi ^df disp(1) elseif varname==3 -%^ ^kw ^df ^ty +%^ ^kw ^df ^bi disp(2) else %^ ^kw @@ -105,13 +105,18 @@ end function dographics(value) f = figure; -%^ ^ty +%^ ^bi ax = axes(f); -%^ ^ty +%^ ^bi set ( ax, 'property',value) -%^ ^ty ^vn ^st +%^ ^bi ^vn ^st + s = open_system('foo.mdl'); +%^ ^si ^st + + set_param(s, 'param', value); +%^ ^si ^vn ^st ^df end function dodebug() @@ -127,21 +132,21 @@ end function mathstuff() myvar = eps + pi + nan + ans + i + NaT + true ; -%^ ^df ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^ty ^cn ^df +%^ ^df ^ma ^bi ^ma ^bi ^ma ^bi ^ma ^bi ^ma ^bi ^ma ^bi ^ma ^df end function helptest() -% HELPTEST has fancy fonts in it. +% HELPTEXT has fancy fonts in it. %^ ^cn end classdef (Abstract) myclass < handle -%^ ^kw ^ty ^fn ^ty ^cn +%^ ^kw ^ty ^fn ^bi ^cn end classdef (Abstract)myclass>1 classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# -%^ ^kw ^ty ^fn ^cn ^ty ^cn ^co +%^ ^kw ^ty ^fn ^cn ^bi ^cn ^co % !!0 % %%% class class @@ -20,7 +20,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# % >> 111 properties (AbortSet=true, NonCopyable=true) % #2# -%^ ^kw ^ty ^cn ^ty ^cn ^co +%^ ^kw ^ty ^ma ^ty ^ma ^co % !!8 AF (1,1) char {mustBeMember(AF, {'High','Medium','Low'})} = 'Low'; % #5# diff --git a/tests/metest.el b/tests/metest.el index a544844..9bc51c7 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -422,12 +422,17 @@ matlab-cross-function-variable-face) ) ( "cn" . font-lock-constant-face ) ( "co" . font-lock-comment-face ) + ( "st" . font-lock-string-face ) + ( "bi" . font-lock-builtin-face ) + ( "cb" . matlab-cellbreak-face ) ( "ig" . matlab-ignored-comment-face ) ( "pr" . matlab-pragma-face ) ( "cd" . matlab-commanddual-string-face ) - ( "st" . font-lock-string-face ) ( "us" . matlab-unterminated-string-face ) + ( "ma" . matlab-math-face ) + ( "si" . matlab-simulink-keyword-face ) + ( "bo" . bold ) ( "df" . nil ) ) From 67eb90e872d869f8775bbb31432a353658032d1e Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 26 Mar 2021 21:05:11 -0400 Subject: [PATCH 130/168] matlab-scan.el: (matlab-line-declaration-p): Fix doc. (matlab-line-first-word-text): New. matlab.el: (matlab-defun-regex): DELETED. (matlab-guess-script-type): Replace use of matlab-defun-regex with new api style navigation. (matlab-guess-function-indentation): New way to guess if a file should have indentation in function body. (matlab-really-gaudy-font-lock-keywords): for tranpose, use builtin-face like other operators. (matlab-mode): When matlab-indent-function-body is 'guess use new function above. (matlab-find-prev-code-line, matlab-find-prev-line) (matlab-prev-line, matlab-valid-end-construct-p): DELETED. (matlab-mode-vf-block-matches-backward): DELETED mlint.el: (mlint-fix-entry): Use new api instead of matlab-find-prev-code-line. tests/metest.el: In end detect test, turn `matlab-indent-function-body' to 'guess. In test, also validate what the indent value is. Put value for indent-function-body back to mw standard when done. tests/empty.m, tests/mclass.m, tests/mfuncends.m tests/mfuncnoends.m, tests/mfuncnoendblock.m: Add indent exptect value to file tests/mfuncnoendindent.m: New file for testing endless function that should be indented. --- matlab-scan.el | 9 +- matlab.el | 210 +++++++++++++++------------------------ mlint.el | 3 +- tests/empty.m | 2 +- tests/mclass.m | 2 +- tests/metest.el | 17 +++- tests/mfuncends.m | 2 +- tests/mfuncnoend.m | 2 +- tests/mfuncnoendblock.m | 2 +- tests/mfuncnoendindent.m | 15 +++ tests/mfuncspacey.m | 2 +- tests/stringtest.m | 2 +- 12 files changed, 123 insertions(+), 145 deletions(-) create mode 100644 tests/mfuncnoendindent.m diff --git a/matlab-scan.el b/matlab-scan.el index 9c45cf6..7c258a4 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -406,7 +406,7 @@ All lines that start with a comment end with a comment." (eq (car lvl1) 'block-start)) (defsubst matlab-line-declaration-p (lvl1) - "If the current line is a declaration, return the column it starts on. + "If the current line is a declaration return non-nil. Declarations are things like function or classdef." (and (matlab-line-block-start-keyword-p lvl1) (eq (nth mlf-stype lvl1) 'decl))) @@ -455,6 +455,13 @@ Return nil for empty and comment only lines." (when endpt (goto-char endpt)))) +(defun matlab-line-first-word-text (&optional lvl1) + "Return text for the specific keyword found under point." + (matlab-with-context-line lvl1 + (buffer-substring-no-properties + (point) (save-excursion (skip-syntax-forward "w_") (point)))) + ) + ;; Parenthetical blocks (defsubst matlab-line-close-paren-p (lvl1) "Non nil If the current line starts with closing paren (any type.)" diff --git a/matlab.el b/matlab.el index 471e037..86e7a1c 100644 --- a/matlab.el +++ b/matlab.el @@ -219,7 +219,6 @@ If the value is 'guess, then we guess if a file has end when )) ) -(defvar matlab-defun-regex) ;; Quiet compiler warning (is defined below) (defvar matlab-last-script-type-guess nil "The last time we guessed the script type, what was it?") (defun matlab-last-guess-decl-p () @@ -229,24 +228,28 @@ If the value is 'guess, then we guess if a file has end when (defun matlab-guess-script-type () "Guess the type of script this `matlab-mode' file contains. Returns one of 'empty, 'script, 'function, 'class." - (setq matlab-last-script-type-guess - (save-excursion - (goto-char (point-min)) - (if (matlab-find-code-line) + (setq + matlab-last-script-type-guess + (save-excursion + (goto-char (point-min)) + (let ((lvl1 nil)) + (cond ((not (matlab-find-code-line)) + 'empty) + ;; We found some code, what is it? - (if (save-excursion - (beginning-of-line) - (looking-at matlab-defun-regex)) - ;; A match - figure out the type of thing. - (let ((str (match-string-no-properties 1))) - (cond ((string= str "function") - 'function) - ((string= str "classdef") - 'class))) - ;; No function or class - just a script. - 'script) - ;; No lines of code, we are empty, so undecided. - 'empty)))) + ((and (setq lvl1 (matlab-compute-line-context 1)) + (matlab-line-declaration-p lvl1)) + ;; We are on a decl - distinguis between type + (let ((str (matlab-line-first-word-text lvl1))) + (cond ((string= str "function") + 'function) + ((string= str "classdef") + 'class) + (t (error "Error in script guessing algorithm."))))) + + (t + ;; No function or class - just a script. + 'script)))))) (defun matlab-do-functions-have-end-p (&optional no-navigate) "Look at the contents of the current buffer and decide if functions have end. @@ -300,6 +303,49 @@ See `matlab-indent-function-body' variable." ;; Else, just return the variable. matlab-indent-function-body)) +(defun matlab-guess-function-indentation () + "Look at the current buffer and determine if functions are indented. +Setup various variables based on what we find." + (let ((st (matlab-guess-script-type)) + ) + (cond + ((not (eq st 'function)) + ;; Anything not a function should follow the mathworks standard. + (setq matlab-indent-function-body 'MathWorks-Standard) + ) + + ;; If we are guessing, keep guessing (vaguely true) + ((eq (matlab-do-functions-have-end-p t) 'guess) + (setq matlab-indent-function-body 'guess)) + + ;; Here it is a function, and there are no ends. + (t + ;; Functions in guess mode we need to find the function decl + ;; and then look at the first code line and see if it is indented + ;; to guess what to do. + (save-excursion + (goto-char (point-min)) + (matlab-find-code-line) + ;; We are likely on the fcn line. Scan to end of it. + (matlab-scan-end-of-command) + ;; Now find next code line after comments + (matlab-find-code-line) + ;; If it is indented, then we too will indent. + (setq matlab-indent-function-body + (if (> (current-indentation) 0) + (if (matlab-do-functions-have-end-p t) + ;; if it indented and we have ends, that is std. + 'MathWorks-Standard + ;; no ends but indented, not the standard. + t) + (if (matlab-do-functions-have-end-p t) + ;; have ends, not indented, force nil. + nil + ;; no ends and not indented, mw stadnard + 'MathWorks-Standard))) + ))) + )) + (defcustom matlab-fill-fudge 10 "Number of characters around `fill-column' we can fudge filling. Basically, there are places that are very convenient to fill at, but @@ -384,8 +430,6 @@ point, but it will be restored for them." '(matlab-mode-vf-functionname matlab-mode-vf-classname matlab-mode-vf-add-ends - matlab-mode-vf-block-matches-forward - matlab-mode-vf-block-matches-backward matlab-mode-vf-quiesce-buffer )))) @@ -970,20 +1014,19 @@ This matcher will handle a range of variable features." (defconst matlab-file-basic-font-lock-keywords (append matlab-basic-font-lock-keywords - (list - ;; MCOS keywords like properties, methods, events - '(matlab-font-lock-mcos-keyword-match + '(;; MCOS keywords like properties, methods, events + (matlab-font-lock-mcos-keyword-match (1 font-lock-keyword-face)) - ;; ARGUMENTS keyword - '(matlab-font-lock-args-keyword-match + ;; ARGUMENTS keyword + (matlab-font-lock-args-keyword-match (1 font-lock-keyword-face)) - ;; Highlight cross function variables - '(matlab-font-lock-cross-function-variables-match + ;; Highlight cross function variables + (matlab-font-lock-cross-function-variables-match (1 matlab-cross-function-variable-face prepend)) - ;; Highlight nested function/end keywords - '(matlab-font-lock-nested-function-keyword-match + ;; Highlight nested function/end keywords + (matlab-font-lock-nested-function-keyword-match (0 matlab-nested-function-keyword-face prepend)) - )) + )) "Basic Expressions to highlight in MATLAB Files.") (defconst matlab-function-arguments @@ -1097,7 +1140,8 @@ This matcher will handle a range of variable features." ;; Since it's a math language, how bout dem symbols? '("\\([<>~=]=\\|\\.[/\\*^'?]\\|\\_<\\(?:\\\\|[-<>!?^&|*+\\/~:@]\\)" 1 font-lock-builtin-face) - '("[]A-Za-z0-9_\"})']\\('+\\)" 1 font-lock-type-face) + ;; highlight transpose + '("[]A-Za-z0-9_\"})']\\('+\\)" 1 font-lock-builtin-face) ;; How about references in the HELP text. (list (concat "^" matlab-comment-line-s "\\s-*" "\\(\\([A-Z]+\\s-*=\\s-+\\|\\[[^]]+]\\s-*=\\s-+\\|\\)" @@ -1342,32 +1386,8 @@ All Key Bindings: ) ((eq matlab-indent-function-body 'guess) - (save-excursion - (goto-char (point-max)) - - (if (re-search-backward matlab-defun-regex nil t) - (let ((beg (point)) - end ; filled in later - (cc (current-column)) - ) - (setq end (if matlab-functions-have-end - (progn (forward-line 0) (point)) - (point-max))) - (goto-char beg) - (catch 'done - (while (progn (forward-line 1) (< (point) end)) - (if (looking-at "\\s-*\\(%\\|$\\)") - nil ; go on to next line - (looking-at "\\s-*") - (goto-char (match-end 0)) - (setq matlab-indent-function-body (> (current-column) cc)) - (throw 'done nil)))) - ) - (setq matlab-indent-function-body 'MathWorks-Standard) - )) + (matlab-guess-function-indentation) ) - - (t) ) ;; When leaving matlab-mode, turn off mlint @@ -1423,43 +1443,6 @@ INTERACTIVE is ignored." (interactive) (message "matlab-mode, version %s" matlab-mode-version)) -(defun matlab-find-prev-code-line () - "Navigate backward until a code line is found. -Navigate across continuations until we are at the beginning of -that command. -Return t on success, nil if we couldn't navigate backwards." - (let ((ans (matlab-find-prev-line 'ignore-comments))) - (when ans - (matlab-scan-beginning-of-command) - ans))) - -(defun matlab-find-prev-line (&optional ignorecomments) - "Recurse backwards until a code line is found." - (if ignorecomments - ;; This version is now super easy, as this built-in - ;; skips comments and whitespace. Nil on bobp. - (progn - (beginning-of-line) - (forward-comment -100000) - (not (bobp))) - ;; Else, scan backward at least 1 step. nil if bob - (if (= -1 (forward-line -1)) nil - ;; Now scan backwards iteratively - (catch 'moose - (let ((lvl1 (matlab-compute-line-context 1))) - (while (or (matlab-line-empty-p lvl1) - (matlab-line-comment-ignore-p lvl1) - ) - (when (= -1 (forward-line -1)) - (throw 'moose nil)))) - t)))) - -(defun matlab-prev-line () - "Go to the previous line of code or comment. Return nil if not found." - (interactive) - (let ((old-point (point))) - (if (matlab-find-prev-line) t (goto-char old-point) nil))) - (defun matlab-find-code-line () "Walk forwards until we are on a line of code return t on success. If the currnet line is code, return immediately. @@ -1467,26 +1450,12 @@ Ignore comments and whitespace." (forward-comment 100000) (not (eobp))) -(defun matlab-valid-end-construct-p () - "Return non-nil if the end after point terminates a block. -Return nil if it is being used to dereference an array." - (if (eq (preceding-char) ?.) - ;; This is a struct field, not valid. - nil - (let ((pps (syntax-ppss (point)))) - ;; If we are in a set of parenthisis, then not valid b/c it is - ;; likely an array reference. Valid == 0 paren depth. - (= (nth 0 pps) 0)))) - ;;; Regexps for MATLAB language =============================================== ;; "-pre" means "partial regular expression" ;; "-if" and "-no-if" means "[no] Indent Function" -(defconst matlab-defun-regex "^\\s-*\\(function\\|classdef\\)[ \t.[]" - "Regular expression defining the beginning of a MATLAB function.") - (defconst matlab-mcos-innerblock-regexp "properties\\|methods\\|events\\|enumeration\\|arguments" "Keywords which mark the beginning of mcos blocks. These keywords can be overriden as variables or functions in other contexts @@ -1692,8 +1661,8 @@ Travells a cross continuations" (if (or (null (matlab-line-regular-comment-p lvl)) (bobp)) nil - ;; We use forward-line and not matlab-prev-line because - ;; we want blank lines to terminate this indentation method. + ;; We use forward-line -1 and not matlab-previous-command-begin + ;; because we want blank lines to terminate this indentation method. (forward-line -1) (matlab-line-end-comment-column (matlab-compute-line-context 1)))))) @@ -3257,29 +3226,6 @@ by `matlab-mode-vf-add-ends'" (save-excursion (insert "\nend\n\n")) (matlab-indent-line)))) -(defun matlab-mode-vf-block-matches-backward (&optional fast) - "Verify/fix unstarted (or dangling end) blocks. -Optional argument FAST causes this check to be skipped." - (goto-char (point-max)) - (let ((go t) (expr (matlab-keyword-regex 'end))) - (matlab-navigation-syntax - (while (and (not fast) go (matlab-re-search-keyword-backward expr nil t)) - (forward-word 1) - (let ((s (point))) - (condition-case nil - (if (and (not (matlab-cursor-in-string-or-comment)) - (matlab-valid-end-construct-p)) - (matlab-backward-sexp) - (backward-word 1)) - (error (setq go nil))) - (if (and (not go) (goto-char s) - (not (matlab-mode-highlight-ask - (point) (save-excursion (backward-word 1) (point)) - "Unstarted block. Continue anyway?"))) - (error "Unstarted Block found!"))) - (message "Block-check: %d%%" - (+ (/ (/ (* 100 (- (point-max) (point))) (point-max)) 2) 50)))))) - ;;; Utility for verify/fix actions if you need to highlight ;; a section of the buffer for the user's approval. (defun matlab-mode-highlight-ask (begin end prompt) diff --git a/mlint.el b/mlint.el index 4ad4115..4516022 100644 --- a/mlint.el +++ b/mlint.el @@ -661,7 +661,8 @@ Optional arguments FIELDS are the initialization arguments." (progn (beginning-of-line) ;; skip over comments that might be headers to the found function. - (matlab-find-prev-code-line) + (matlab-previous-command-begin + (matlab-compute-line-context 2)) ;;(matlab-find-prev-code-line) (forward-line 1) (save-excursion (insert "end\n\n")) (matlab-indent-line)) diff --git a/tests/empty.m b/tests/empty.m index 8f9eb09..7313e86 100644 --- a/tests/empty.m +++ b/tests/empty.m @@ -1,2 +1,2 @@ % Empty M file. -% %%%empty guess \ No newline at end of file +% %%%empty guess guess \ No newline at end of file diff --git a/tests/mclass.m b/tests/mclass.m index 742b024..79d97fb 100644 --- a/tests/mclass.m +++ b/tests/mclass.m @@ -2,7 +2,7 @@ classdef (abstract) mclass < handle & matlab.mixin.SetGetExactNames % #7# %^ ^kw ^ty ^fn ^cn ^bi ^cn ^co % !!0 -% %%% class class +% %%% class class class % >>11 properties (Access='public') % #2# diff --git a/tests/metest.el b/tests/metest.el index 9bc51c7..5d6d9b5 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -47,7 +47,10 @@ (metest-log-init) + (setq-default matlab-indent-function-body 'guess) ;; Force the guess system to be exercised. (metest-run 'metest-end-detect-test) + (setq-default matlab-indent-function-body 'MathWorks-Standard) ;; put it back + (metest-run 'metest-comment-string-syntax-test) (metest-run 'metest-sexp-counting-test) (metest-run 'metest-sexp-traversal-test) @@ -97,7 +100,7 @@ 0) )) -(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" "mfuncspacey.m" ) +(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" "mfuncspacey.m" "mfuncnoendindent.m" ) "List of files for running end detection tests on.") (defvar metest-end-detect-test (cons "END detection" met-end-detect-files)) @@ -109,19 +112,25 @@ (with-current-buffer buf (goto-char (point-min)) ;;(message ">> Checking END detection in %S" (current-buffer)) - (if (re-search-forward "%%%\\s-*\\(\\w+\\)\\s-+\\(\\w+\\)$" nil t) + (if (re-search-forward "%%%\\s-*\\(\\w+\\)\\s-+\\(\\w+\\)\\s-+\\(\\w+\\)$" nil t) (let ((st-expect (intern (match-string-no-properties 1))) (end-expect (intern (match-string-no-properties 2))) + (indent-expect (intern (match-string-no-properties 3))) (st-actual (matlab-guess-script-type)) - (end-actual (matlab-do-functions-have-end-p))) + (end-actual (matlab-do-functions-have-end-p)) + (indent-actual (matlab-indent-function-body-p)) + ) (unless (eq st-actual st-expect) (metest-error "Script type detection failure: Expected %s but found %s" st-expect st-actual)) (unless (eq end-actual end-expect) (metest-error "Script end detection failure: Expected %s but found %s" end-expect end-actual)) + (unless (eq indent-actual indent-expect) + (metest-error "Script indent detection failure: Expected %s but found %s" + indent-expect indent-actual)) - (setq ret (list st-actual end-actual)) + (setq ret (list "script[" st-actual "] end[" end-actual "] indent-p[" indent-actual "]")) ;;(message "<< Script type and end detection passed: %s, %s" st-actual end-actual) ) ;; No expected values found in the file. diff --git a/tests/mfuncends.m b/tests/mfuncends.m index 5a31078..9201469 100644 --- a/tests/mfuncends.m +++ b/tests/mfuncends.m @@ -3,7 +3,7 @@ function mfuncends( ) % !!0 % Used with test harness to validate indentation and end detection. !!0 % !!0 -% %%%function function +% %%%function function function fcn_call(); %!!4 diff --git a/tests/mfuncnoend.m b/tests/mfuncnoend.m index 0dfff4d..51e12ab 100644 --- a/tests/mfuncnoend.m +++ b/tests/mfuncnoend.m @@ -1,7 +1,7 @@ function mfuncnoend % A function file that does not have ends at the end of functions. % -% %%% function nil +% %%% function nil nil fcn_call(1) diff --git a/tests/mfuncnoendblock.m b/tests/mfuncnoendblock.m index a3ca026..18a2fa6 100644 --- a/tests/mfuncnoendblock.m +++ b/tests/mfuncnoendblock.m @@ -6,7 +6,7 @@ function mfuncnoendblock % A function file that does not have ends at the end of functions. % !!0 -% %%% function nil +% %%% function nil nil fcn_call(1) %!!0 diff --git a/tests/mfuncnoendindent.m b/tests/mfuncnoendindent.m new file mode 100644 index 0000000..18f6cc2 --- /dev/null +++ b/tests/mfuncnoendindent.m @@ -0,0 +1,15 @@ +function mfuncnoendindent +% A function file that does not have ends at the end of functions. +% +% %%% function nil t + + fcn_call(1) + + +function fcn_call(idx) + + if idx > 0 + fcn_call(ix-1) + end + + diff --git a/tests/mfuncspacey.m b/tests/mfuncspacey.m index 607af51..53c708b 100644 --- a/tests/mfuncspacey.m +++ b/tests/mfuncspacey.m @@ -3,7 +3,7 @@ % % Used with test harness to validate indentation and end detection. % - % %%%function function + % %%%function function function fcn_call(); diff --git a/tests/stringtest.m b/tests/stringtest.m index 2d57ce1..c47437b 100644 --- a/tests/stringtest.m +++ b/tests/stringtest.m @@ -2,7 +2,7 @@ % % #c# % -% %%%script script +% %%%script script script %% Basic strings From a20a946e20b4193e1fc85f5d0028a6c3f0bdd6c0 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Mar 2021 09:14:44 -0400 Subject: [PATCH 131/168] matlab.el: (matlab-mode-verify-fix-file): Re-write to use mapc. Always call `matlab-mode-vf-guess-functions-have-end'. (matlab-mode-vf-guess-functions-have-end): NEW copied from below. (matlab-mode-vf-block-matches-forward): Remove code that is now in above fcn. Replace with small check to auto-select fast mode. --- matlab.el | 147 +++++++++++++++++++++++++++++------------------------- 1 file changed, 79 insertions(+), 68 deletions(-) diff --git a/matlab.el b/matlab.el index 86e7a1c..40092f0 100644 --- a/matlab.el +++ b/matlab.el @@ -2963,30 +2963,7 @@ Returns a list: \(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)" (list here-beg here-end there-beg there-end mismatch) )))))) -;;; M Code verification & Auto-fix ============================================ - -(defun matlab-mode-verify-fix-file-fn () - "Verify the current buffer from `write-contents-hooks'." - (if matlab-verify-on-save-flag - (matlab-mode-verify-fix-file (> (point-max) - matlab-block-verify-max-buffer-size))) - ;; Always return nil. - nil) - -(defun matlab-mode-verify-fix-file (&optional fast) - "Verify the current buffer satisfies all M things that might be useful. -We will merely loop across a list of verifiers/fixers in -`matlab-mode-verify-fix-functions'. -If optional FAST is non-nil, do not perform usually lengthy checks." - (interactive) - (let ((p (point)) - (l matlab-mode-verify-fix-functions)) - (while l - (funcall (car l) fast) - (setq l (cdr l))) - (goto-char p)) - (if (matlab-called-interactively-p) - (message "Done."))) +;;; M Code verification ============================================ (defun matlab-toggle-show-mlint-warnings () "Toggle `matlab-show-mlint-warnings'." @@ -3017,9 +2994,83 @@ If optional FAST is non-nil, do not perform usually lengthy checks." matlab-show-mlint-warnings) 1 -1))) ; change mlint mode altogether +;;; Verify / Auto-fix ============================================ + +(defun matlab-mode-verify-fix-file-fn () + "Verify the current buffer from `write-contents-hooks'." + (if matlab-verify-on-save-flag + (matlab-mode-verify-fix-file (> (point-max) + matlab-block-verify-max-buffer-size))) + ;; Always return nil. + nil) + +(defun matlab-mode-verify-fix-file (&optional fast) + "Verify the current buffer satisfies all M things that might be useful. +We will merely loop across a list of verifiers/fixers in +`matlab-mode-verify-fix-functions'. +If optional FAST is non-nil, do not perform usually lengthy checks." + (interactive) + (save-excursion + ;; Always re-validate if functions have end. + (matlab-mode-vf-guess-functions-have-end fast) + ;; Loop over the options. + (mapc (lambda (func) (funcall func fast)) + matlab-mode-verify-fix-functions)) + (if (matlab-called-interactively-p) + (message "Done."))) + ;; ;; Add more auto verify/fix functions here! ;; +(defun matlab-mode-vf-guess-functions-have-end (&optional fast) + "Look at the current buffer state and decide determine if functions have end. +If this is already known, no action is taken." + (let ((filetype (matlab-guess-script-type))) + + ;; Lets if if the file if we were in still doesn't know what to do + ;; a bout ends, and and re-assert what we should do. + (cond + ;; If the file is empty of code (from before, or just now) + ;; then optimize out this step. + ((eq filetype 'empty) + ;; If user deleted content, go back into guess mode. + (setq matlab-functions-have-end 'guess) + (matlab-functions-have-end-minor-mode 1) + ) + + ;; If there is just bad syntax somewhere, skip it with a notice. + ((save-excursion (goto-char (point-max)) (matlab-in-list-p)) + (setq matlab-functions-have-end 'guess) + (matlab-functions-have-end-minor-mode 1) + (message "Unterminated list - skipping block check")) + + ;; If we are in guess mode, but user added content, we can + ;; not have a fresh new guess. + ((eq matlab-functions-have-end 'guess) + (let ((guess (matlab-do-functions-have-end-p 'no-navigate))) + (if guess (matlab-functions-have-end-minor-mode 1) + (matlab-functions-have-end-minor-mode -1))) + ) + + ;; If we are in no-end mode, BUT the filetype is wrong, say something. + ((and (not matlab-functions-have-end) (or (eq filetype 'script) (eq filetype 'class))) + (message "Type of file detected no longer matches `matlab-functions-have-end' of nil, assume t.") + (matlab-functions-have-end-minor-mode 1) + (sit-for 1) + ) + + ;; If functions have end but the style changes, re-up the lighter on the minor mode. + ;; note, we can ignore that 'empty == 'guess b/c handled earlier. + ((and matlab-functions-have-end (not (eq matlab-functions-have-end filetype))) + (matlab-functions-have-end-minor-mode 1)) + + ;; If the variable was specified and file is not empty, then do nothing. + ;; TODO - maybe we should force to t for scripts and classes? + + ) ;; end cond + + )) + (defun matlab-mode-vf-functionname (&optional fast) "Verify/Fix the function name of this file. Optional argument FAST is ignored." @@ -3099,50 +3150,10 @@ by `matlab-mode-vf-add-ends'" ;; Before checking syntax, lets re-look at the file if we were in ;; guess mode and re-assert what we should do. - (cond - ;; If the file is empty of code (from before, or just now) - ;; then optimize out this step. - ((eq filetype 'empty) - ;; No code, no need to loop. - (setq fast t) - ;; If user deleted content, go back into guess mode. - (setq matlab-functions-have-end 'guess) - (matlab-functions-have-end-minor-mode 1) - ) - - ;; If there is just bad syntax somewhere, skip it with a notice. - ((save-excursion (goto-char (point-max)) (matlab-in-list-p)) - (setq fast t) - ;; Let user fix it later - (setq matlab-functions-have-end 'guess) - (matlab-functions-have-end-minor-mode 1) - (message "Unterminated list - skipping block check")) - - - ;; If we are in guess mode, but user added content, we can - ;; not have a fresh new guess. - ((eq matlab-functions-have-end 'guess) - (let ((guess (matlab-do-functions-have-end-p 'no-navigate))) - (if guess (matlab-functions-have-end-minor-mode 1) - (matlab-functions-have-end-minor-mode -1))) - ) - - ;; If we are in no-end mode, BUT the filetype is wrong, say something. - ((and (not matlab-functions-have-end) (or (eq filetype 'script) (eq filetype 'class))) - (message "Type of file detected no longer matches `matlab-functions-have-end' of nil, assume t.") - (matlab-functions-have-end-minor-mode 1) - (sit-for 1) - ) - - ;; If functions have end but the style changes, re-up the lighter on the minor mode. - ;; note, we can ignore that 'empty == 'guess b/c handled earlier. - ((and matlab-functions-have-end (not (eq matlab-functions-have-end filetype))) - (matlab-functions-have-end-minor-mode 1)) - - ;; If the variable was specified and file is not empty, then do nothing. - ;; TODO - maybe we should force to t for scripts and classes? - - ) ;; end cond + (when (or (eq filetype 'empty) + (save-excursion (goto-char (point-max)) (matlab-in-list-p))) + ;; In a bad state - go fast. + (setq fast t)) ;; compute expression after changing state of funtions have end above. (setq expr (concat "\\<\\(" (matlab-block-beg-pre) "\\)\\>")) From 2f6958d102dab3eac96ebbf6fca03b5ff39e2e14 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Mar 2021 10:01:56 -0400 Subject: [PATCH 132/168] matlab.el: (matlab-frame-init): Delete as function. Content is no longer in a fcn since menus supported in terminal mode. (matlab-mode): Stop calling frame init. (matlab-mode-vf-block-matches-forward): Rewrite to use new APIs for searching for keywords and getting locations of the keywords. --- matlab.el | 368 +++++++++++++++++++++++++----------------------------- 1 file changed, 170 insertions(+), 198 deletions(-) diff --git a/matlab.el b/matlab.el index 40092f0..4711bc7 100644 --- a/matlab.el +++ b/matlab.el @@ -538,89 +538,83 @@ point, but it will be restored for them." (defvar matlab-mode-menu-keymap nil "Keymap used in MATLAB mode to provide a menu.") -(defun matlab-frame-init () - "Initialize Emacs menu system." - (interactive) - ;; make a menu keymap - (easy-menu-define - matlab-mode-menu - matlab-mode-map - "MATLAB menu" - '("MATLAB" - ["Start MATLAB" matlab-shell - :active (not (matlab-shell-active-p)) - :visible (not (matlab-shell-active-p)) ] - ["Switch to MATLAB" matlab-shell +;; make a menu keymap +(easy-menu-define matlab-mode-menu matlab-mode-map "MATLAB menu" + '("MATLAB" + ["Start MATLAB" matlab-shell + :active (not (matlab-shell-active-p)) + :visible (not (matlab-shell-active-p)) ] + ["Switch to MATLAB" matlab-shell + :active (matlab-any-shell-active-p) + :visible (matlab-any-shell-active-p)] + ["Save and go" matlab-shell-save-and-go + :active (matlab-any-shell-active-p) ] + ["Run Region" matlab-shell-run-region + :active (matlab-any-shell-active-p) ] + ["Run Cell" matlab-shell-run-cell + :active (matlab-any-shell-active-p) ] + ["Version" matlab-show-version t] + "----" + ["Locate MATLAB function" matlab-shell-locate-fcn + :active (matlab-shell-active-p) + :help "Run 'which FCN' in matlab-shell, then open the file in Emacs"] + ["Show M-Lint Warnings" matlab-toggle-show-mlint-warnings + :active (and (locate-library "mlint") (fboundp 'mlint-minor-mode)) + :style toggle :selected matlab-show-mlint-warnings + ] + ("Auto Fix" + ["Verify/Fix source" matlab-mode-verify-fix-file t] + ["Spell check strings and comments" matlab-ispell-strings-and-comments t] + ["Quiesce source" matlab-mode-vf-quiesce-buffer t] + ) + ("Navigate" + ["Beginning of Command" matlab-beginning-of-command t] + ["End of Command" matlab-end-of-command t] + ["Forward Block" matlab-forward-sexp t] + ["Backward Block" matlab-backward-sexp t] + ["Beginning of Function" matlab-beginning-of-defun t] + ["End of Function" matlab-end-of-defun t]) + ("Format" + ["Justify Line" matlab-justify-line t] + ["Fill Region" matlab-fill-region t] + ["Fill Comment Paragraph" matlab-fill-paragraph + (save-excursion (matlab-comment-on-line))] + ["Join Comment" matlab-join-comment-lines + (save-excursion (matlab-comment-on-line))] + ["Comment Region" matlab-comment-region t] + ["Uncomment Region" matlab-uncomment-region t] + ["Indent Syntactic Block" matlab-indent-sexp]) + ("Debug" + ["Edit File (toggle read-only)" matlab-shell-gud-mode-edit + :help "Exit MATLAB debug minor mode to edit without exiting MATLAB's K>> prompt." + :visible gud-matlab-debug-active ] + ["Add Breakpoint (ebstop in FILE at point)" gud-break + :active (matlab-shell-active-p) + :help "When MATLAB debugger is active, set break point at current M-file point"] + ["Remove Breakpoint (ebclear in FILE at point)" gud-remove + :active (matlab-shell-active-p) + :help "Show all active breakpoints in a separate buffer." ] + ["List Breakpoints (ebstatus)" gud-list-breakpoints + :active (matlab-shell-active-p) + :help "List active breakpoints."] + ["Step (dbstep in)" gud-step + :active gud-matlab-debug-active + :help "When MATLAB debugger is active, step into line"] + ["Next (dbstep)" gud-next + :active gud-matlab-debug-active + :help "When MATLAB debugger is active, step one line"] + ["Finish function (dbstep out)" gud-finish + :active gud-matlab-debug-active + :help "When MATLAB debugger is active, run to end of function"] + ["Continue (dbcont)" gud-cont + :active gud-matlab-debug-active + :help "When MATLAB debugger is active, run to next break point or finish"] + ["Evaluate Expression" matlab-shell-gud-show-symbol-value :active (matlab-any-shell-active-p) - :visible (matlab-any-shell-active-p)] - ["Save and go" matlab-shell-save-and-go - :active (matlab-any-shell-active-p) ] - ["Run Region" matlab-shell-run-region - :active (matlab-any-shell-active-p) ] - ["Run Cell" matlab-shell-run-cell - :active (matlab-any-shell-active-p) ] - ["Version" matlab-show-version t] - "----" - ["Locate MATLAB function" matlab-shell-locate-fcn - :active (matlab-shell-active-p) - :help "Run 'which FCN' in matlab-shell, then open the file in Emacs"] - ["Show M-Lint Warnings" matlab-toggle-show-mlint-warnings - :active (and (locate-library "mlint") (fboundp 'mlint-minor-mode)) - :style toggle :selected matlab-show-mlint-warnings - ] - ("Auto Fix" - ["Verify/Fix source" matlab-mode-verify-fix-file t] - ["Spell check strings and comments" matlab-ispell-strings-and-comments t] - ["Quiesce source" matlab-mode-vf-quiesce-buffer t] - ) - ("Navigate" - ["Beginning of Command" matlab-beginning-of-command t] - ["End of Command" matlab-end-of-command t] - ["Forward Block" matlab-forward-sexp t] - ["Backward Block" matlab-backward-sexp t] - ["Beginning of Function" matlab-beginning-of-defun t] - ["End of Function" matlab-end-of-defun t]) - ("Format" - ["Justify Line" matlab-justify-line t] - ["Fill Region" matlab-fill-region t] - ["Fill Comment Paragraph" matlab-fill-paragraph - (save-excursion (matlab-comment-on-line))] - ["Join Comment" matlab-join-comment-lines - (save-excursion (matlab-comment-on-line))] - ["Comment Region" matlab-comment-region t] - ["Uncomment Region" matlab-uncomment-region t] - ["Indent Syntactic Block" matlab-indent-sexp]) - ("Debug" - ["Edit File (toggle read-only)" matlab-shell-gud-mode-edit - :help "Exit MATLAB debug minor mode to edit without exiting MATLAB's K>> prompt." - :visible gud-matlab-debug-active ] - ["Add Breakpoint (ebstop in FILE at point)" gud-break - :active (matlab-shell-active-p) - :help "When MATLAB debugger is active, set break point at current M-file point"] - ["Remove Breakpoint (ebclear in FILE at point)" gud-remove - :active (matlab-shell-active-p) - :help "Show all active breakpoints in a separate buffer." ] - ["List Breakpoints (ebstatus)" gud-list-breakpoints - :active (matlab-shell-active-p) - :help "List active breakpoints."] - ["Step (dbstep in)" gud-step - :active gud-matlab-debug-active - :help "When MATLAB debugger is active, step into line"] - ["Next (dbstep)" gud-next - :active gud-matlab-debug-active - :help "When MATLAB debugger is active, step one line"] - ["Finish function (dbstep out)" gud-finish - :active gud-matlab-debug-active - :help "When MATLAB debugger is active, run to end of function"] - ["Continue (dbcont)" gud-cont - :active gud-matlab-debug-active - :help "When MATLAB debugger is active, run to next break point or finish"] - ["Evaluate Expression" matlab-shell-gud-show-symbol-value - :active (matlab-any-shell-active-p) - :help "When MATLAB is active, show value of the symbol under point."] - ["Show Stack" mlg-show-stack - :active gud-matlab-debug-active - :help "When MATLAB debugger is active, show the stack in a buffer."] + :help "When MATLAB is active, show value of the symbol under point."] + ["Show Stack" mlg-show-stack + :active gud-matlab-debug-active + :help "When MATLAB debugger is active, show the stack in a buffer."] ;;; Advertise these more if we can get them working w/ gud's frame show. ;;; ["Up Call Stack (dbup)" gud-up ;;; :active gud-matlab-debug-active @@ -628,59 +622,59 @@ point, but it will be restored for them." ;;; ["Down Call Stack (dbdown)" gud-down ;;; :active gud-matlab-debug-active ;;; :help "When MATLAB debugger is active and at break point, go down a frame"] - ["Quit debugging (dbquit)" gud-stop-subjob - :active gud-matlab-debug-active - :help "When MATLAB debugger is active, stop debugging"] - ) + ["Quit debugging (dbquit)" gud-stop-subjob + :active gud-matlab-debug-active + :help "When MATLAB debugger is active, stop debugging"] + ) -;; TODO - how to autoload these? Do we want this menu? -;; ("Insert" -;; ["Complete Symbol" matlab-complete-symbol t] -;; ["Comment" matlab-comment t] -;; ["if end" tempo-template-matlab-if t] -;; ["if else end" tempo-template-matlab-if-else t] -;; ["for end" tempo-template-matlab-for t] -;; ["switch otherwise end" tempo-template-matlab-switch t] -;; ["Next case" matlab-insert-next-case t] -;; ["try catch end" tempo-template-matlab-try t] -;; ["while end" tempo-template-matlab-while t] -;; ["End of block" matlab-insert-end-block t] -;; ["Function" tempo-template-matlab-function t] -;; ["Stringify Region" matlab-stringify-region t] -;; ) - ("Customize" - ["Indent Function Body" - (setq matlab-indent-function-body (not (matlab-indent-function-body-p))) - :style toggle :selected matlab-indent-function-body] - ["Functions Have end" - matlab-toggle-functions-have-end - :style toggle :selected matlab-functions-have-end] - ["Verify File on Save" - (setq matlab-verify-on-save-flag (not matlab-verify-on-save-flag)) - :style toggle :selected matlab-verify-on-save-flag] - ["Auto Fill does Code" - (setq matlab-fill-code (not matlab-fill-code)) - :style toggle :selected matlab-fill-code ] - ["Highlight Cross-Function Variables" - matlab-toggle-highlight-cross-function-variables - :active (locate-library "mlint") - :style toggle :selected matlab-highlight-cross-function-variables - ] - ["Add Needed Semicolon on RET" - (setq matlab-return-add-semicolon (not matlab-return-add-semicolon)) - :style toggle :selected matlab-return-add-semicolon - ] - ["Customize" (customize-group 'matlab) - (and (featurep 'custom) (fboundp 'custom-declare-variable)) - ] - ) - "----" - ["Run M Command" matlab-shell-run-command (matlab-shell-active-p)] - ["Describe Command" matlab-shell-describe-command (matlab-shell-active-p)] - ["Describe Variable" matlab-shell-describe-variable (matlab-shell-active-p)] - ["Command Apropos" matlab-shell-apropos (matlab-shell-active-p)] - )) - (easy-menu-add matlab-mode-menu matlab-mode-map)) + ;; TODO - how to autoload these? Do we want this menu? + ;; ("Insert" + ;; ["Complete Symbol" matlab-complete-symbol t] + ;; ["Comment" matlab-comment t] + ;; ["if end" tempo-template-matlab-if t] + ;; ["if else end" tempo-template-matlab-if-else t] + ;; ["for end" tempo-template-matlab-for t] + ;; ["switch otherwise end" tempo-template-matlab-switch t] + ;; ["Next case" matlab-insert-next-case t] + ;; ["try catch end" tempo-template-matlab-try t] + ;; ["while end" tempo-template-matlab-while t] + ;; ["End of block" matlab-insert-end-block t] + ;; ["Function" tempo-template-matlab-function t] + ;; ["Stringify Region" matlab-stringify-region t] + ;; ) + ("Customize" + ["Indent Function Body" + (setq matlab-indent-function-body (not (matlab-indent-function-body-p))) + :style toggle :selected matlab-indent-function-body] + ["Functions Have end" + matlab-toggle-functions-have-end + :style toggle :selected matlab-functions-have-end] + ["Verify File on Save" + (setq matlab-verify-on-save-flag (not matlab-verify-on-save-flag)) + :style toggle :selected matlab-verify-on-save-flag] + ["Auto Fill does Code" + (setq matlab-fill-code (not matlab-fill-code)) + :style toggle :selected matlab-fill-code ] + ["Highlight Cross-Function Variables" + matlab-toggle-highlight-cross-function-variables + :active (locate-library "mlint") + :style toggle :selected matlab-highlight-cross-function-variables + ] + ["Add Needed Semicolon on RET" + (setq matlab-return-add-semicolon (not matlab-return-add-semicolon)) + :style toggle :selected matlab-return-add-semicolon + ] + ["Customize" (customize-group 'matlab) + (and (featurep 'custom) (fboundp 'custom-declare-variable)) + ] + ) + "----" + ["Run M Command" matlab-shell-run-command (matlab-shell-active-p)] + ["Describe Command" matlab-shell-describe-command (matlab-shell-active-p)] + ["Describe Variable" matlab-shell-describe-variable (matlab-shell-active-p)] + ["Command Apropos" matlab-shell-apropos (matlab-shell-active-p)] + )) +(easy-menu-add matlab-mode-menu matlab-mode-map) ;;; Font Lock : Character Vectors, Strings and Comments ================================ @@ -1361,9 +1355,9 @@ All Key Bindings: ((?_ . "w")))) (setq font-lock-multiline 'undecided) (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) - - (if window-system (matlab-frame-init)) + (setq show-paren-data-function 'matlab-show-paren-or-block) + ;; If first function is terminated with an end statement, then functions have ;; ends. (if (matlab-do-functions-have-end-p) @@ -3141,8 +3135,9 @@ not be needed. Optional argument FAST causes this check to be skipped. Optional argument ADDEND asks to add ends to functions, and is used by `matlab-mode-vf-add-ends'" - (let ((go t) - (expr nil) + (let ((expr nil) + (scanstate nil) + (exit nil) ;; lets avoid asking questions based on id of this file ;; and if ends are optional in the first place. (filetype (matlab-guess-script-type)) @@ -3155,71 +3150,48 @@ by `matlab-mode-vf-add-ends'" ;; In a bad state - go fast. (setq fast t)) - ;; compute expression after changing state of funtions have end above. - (setq expr (concat "\\<\\(" (matlab-block-beg-pre) "\\)\\>")) - ;; Navigate our sexp's and make sure we're all good. (matlab-navigation-syntax (goto-char (point-min)) - (while (and (not fast) go (re-search-forward expr nil t)) - (forward-word -1) ;back over the special word - (let ((s (point)) - e) - (condition-case nil - (if (and (not (matlab-cursor-in-string-or-comment)) - (not (matlab-block-comment-bounds)) - (or matlab-functions-have-end (not (eq (matlab-on-keyword-p) 'decl)))) - (progn - (matlab-forward-sexp) - (forward-word -1) - (when (not (eq (matlab-on-keyword-p) 'end)) - ;; TODO - is this possible ? - (setq go nil))) - (forward-word 1)) - (error (setq go nil))) + (while (and (not fast) (not exit) + (matlab--scan-next-keyword 'decl (point-max))) + (forward-word -1) + (if (not (setq scanstate (matlab--scan-block-forward))) + ;; This scan returns any leftover unterminated blocks. + ;; If it is empty, we are fine. + (when (eq matlab-functions-have-end 'guess) + (matlab-functions-have-end-minor-mode 1)) - (cond - ;; If we are still in guess mode and file is good, then we now have our answer. - ((and go (eq matlab-functions-have-end 'guess)) - (matlab-functions-have-end-minor-mode 1)) - - ;; If we had an error and still in guess mode we can look at the latest - ;; content and decide if we should have ends anyway. - ((and (not go) (eq matlab-functions-have-end 'guess)) - (if (matlab-do-functions-have-end-p 'no-navigate) - ;; minor mode now looks at file type when passing in 1 - (matlab-functions-have-end-minor-mode 1) - ;; Turn off for this file. No questions. - (matlab-functions-have-end-minor-mode -1))) - - ;; If we had an error, but none of the above, try to fix? - ((not go) - (goto-char s) - (setq e (save-excursion (forward-word 1) (point))) - ;; Try to add an end to the broken block - (if addend - (if (matlab-mode-highlight-ask - s e "Unterminated block. Try to add end?") - (progn + ;; If we had an error, but none of the above, try to fix? + ;; Get locations out of the found keyword. + (save-excursion + (let ((s (nth 2 (car scanstate))) + (e (nth 3 (car scanstate)))) + (goto-char s) + ;; Try to add an end to the broken block + (if addend + (if (matlab-mode-highlight-ask + s e + "Unterminated block. Try to add end?") (matlab-mode-vf-add-end-to-this-block) - (setq go t)) - ;; Else, mark this buffer as not needing ends, - ;; but ONLY if a function buffer - (when (eq filetype 'function) - (if (matlab-mode-highlight-ask - s e "Should funtions have end in this file?") - (matlab-functions-have-end-minor-mode 1) - (matlab-functions-have-end-minor-mode -1) - (message "Marking buffer as not needing END for this session.") - (sit-for 1)))) - ;; We aren't in addend mode then we are in plain verify - ;; mode - (if (matlab-mode-highlight-ask - s e - "Unterminated block. Continue anyway?") - nil ;; continue anyway. - (error "Unterminated Block found!")))) - )) ;; cond, let + ;; Else, mark this buffer as not needing ends, + ;; but ONLY if a function buffer + (when (eq filetype 'function) + (if (matlab-mode-highlight-ask + s e + "Should funtions have end in this file?") + (matlab-functions-have-end-minor-mode 1) + (matlab-functions-have-end-minor-mode -1) + (message "Marking buffer as not needing END for this session.") + (sit-for 1)))) + ;; We aren't in addend mode then we are in plain verify + ;; mode + (if (matlab-mode-highlight-ask + s e + "Unterminated block. Continue anyway?") + nil ;; continue anyway. + (error "Unterminated Block found!"))) + ))) ;; save-excursion, let, if (message "Block-check: %d%%" (/ (/ (* 100 (point)) (point-max)) 2)))))) (defun matlab-mode-vf-add-end-to-this-block () From 21b33abd51e779e8231ff4760a2053cd5756b72a Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Mar 2021 13:58:07 -0400 Subject: [PATCH 133/168] matlab-complete.el: (matlab-find-recent-variable-list): Use new search APIs for keyword searches. Fix some misc bugs. (matlab--complete-compute-search-functions): New - pulled from below: (matlab-complete-symbol-local): Remove code for deriving which functions to use. Pulled out to support testing. matlab.el: (matlab-mcos-innerblock-regexp, matlab-mcos-regexp, matlab-block-beg-pre-if) (matlab-block-beg-pre-no-if, matlab-block-beg-pre): DELETED tests/metest.el: (matlab-complete): New require (metest-all-syntax-tests): Move stuff around. Add metest-complete-test. (met-complete-files, met-complete-tools, metest-complete-test): NEW tests/complete.m: New file for testing local completion stuff. --- matlab-complete.el | 80 ++++++++++++++++++++++++---------------------- matlab.el | 29 ----------------- tests/complete.m | 47 +++++++++++++++++++++++++++ tests/metest.el | 53 ++++++++++++++++++++++++++++-- 4 files changed, 139 insertions(+), 70 deletions(-) create mode 100644 tests/complete.m diff --git a/matlab-complete.el b/matlab-complete.el index 0643c31..0874f9b 100644 --- a/matlab-complete.el +++ b/matlab-complete.el @@ -248,28 +248,28 @@ If the list is empty, then searches continue backwards through the code." (nreverse lst))) (save-excursion (let ((lst nil)) - (while (and (re-search-backward - (concat "\\<\\(" matlab-block-beg-pre-no-if - "\\)\\s-+(?\\s-*\\(" prefix - "\\w+\\)\\>") - bounds t) - (< (length lst) 10)) - (setq lst (cons (match-string 2) lst))) + (while (and + (< (length lst) 10) + (matlab-re-search-keyword-backward (matlab-keyword-regex 'ctrl) bounds t)) + (when (looking-at (concat "\\w+\\s-+(?\\(" prefix "\\w+\\)\\_>")) + (setq lst (cons (match-string 1) lst)))) (nreverse lst))) (save-excursion - (if (re-search-backward "^\\s-*global\\s-+" bounds t) - (let ((lst nil) m e) + (let ((lst nil) m e) + (while (matlab-re-search-keyword-backward + (matlab-keyword-regex 'vardecl) bounds t) + (save-excursion (goto-char (match-end 0)) - (while (looking-at "\\(\\w+\\)\\([ \t]+\\|$\\)") + (while (looking-at "\\s-*\\(\\w+\\)\\([ \t]+\\|$\\)") (setq m (match-string 1) e (match-end 0)) (if (equal 0 (string-match prefix m)) (setq lst (cons m lst))) - (goto-char e)) - (nreverse lst)))) + (goto-char e)))) + (nreverse lst))) (save-excursion (if (and (re-search-backward "^\\s-*function\\>" bounds t) - (re-search-forward "\\<\\(\\w+\\)(" + (re-search-forward "\\_<\\(\\w+\\)\\s-*(" (matlab-point-at-eol) t)) (let ((lst nil) m e) (while (looking-at "\\(\\w+\\)\\s-*[,)]\\s-*") @@ -315,7 +315,7 @@ In NEXT is non-nil, than continue through the list of elements." (let ((lst nil)) (while (re-search-forward "^\\s-*function\\>" nil t) (if (re-search-forward - (concat "\\(" prefix "\\w+\\)\\s-*\\($\\|(\\)") + (concat "\\_<\\(" prefix "\\w+\\)\\s-*\\($\\|(\\)") (matlab-point-at-eol) t) (setq lst (cons (match-string 1) lst)))) (nreverse lst))) @@ -463,7 +463,33 @@ Use `completion-in-region' to support the completion behavior." (completion-in-region common-substr-start-pt common-substr-end-pt completions) )) ) - + +(defun matlab--complete-compute-search-functions (semantics) + "Return the search functions for context specified by SEMATNICS." + (cond ((eq semantics 'solo) + '(matlab-solo-completions + matlab-find-user-functions + matlab-find-recent-variable)) + ((eq semantics 'boolean) + '(matlab-find-recent-variable + matlab-boolean-completions + matlab-find-user-functions + matlab-value-completions)) + ((eq semantics 'value) + '(matlab-find-recent-variable + matlab-find-user-functions + matlab-value-completions + matlab-boolean-completions)) + ((eq semantics 'property) + '(matlab-property-completions + matlab-find-user-functions + matlab-find-recent-variable + matlab-value-completions)) + (t '(matlab-find-recent-variable + matlab-find-user-functions + matlab-value-completions + matlab-boolean-completions)))) + (defun matlab-complete-symbol-local (&optional arg) "Complete a partially typed symbol in a MATLAB mode buffer. If the previously entered command was also `matlab-complete-symbol' @@ -496,29 +522,7 @@ to change it temporarily." (setq matlab-last-prefix prefix matlab-last-semantic sem matlab-completion-search-state - (cond ((eq sem 'solo) - '(matlab-solo-completions - matlab-find-user-functions - matlab-find-recent-variable)) - ((eq sem 'boolean) - '(matlab-find-recent-variable - matlab-boolean-completions - matlab-find-user-functions - matlab-value-completions)) - ((eq sem 'value) - '(matlab-find-recent-variable - matlab-find-user-functions - matlab-value-completions - matlab-boolean-completions)) - ((eq sem 'property) - '(matlab-property-completions - matlab-find-user-functions - matlab-find-recent-variable - matlab-value-completions)) - (t '(matlab-find-recent-variable - matlab-find-user-functions - matlab-value-completions - matlab-boolean-completions))))) + (matlab--complete-compute-search-functions sem))) (cond ((eq matlab-completion-technique 'increment) (let ((r nil) (donext (eq last-command 'matlab-complete-symbol))) diff --git a/matlab.el b/matlab.el index 4711bc7..3967176 100644 --- a/matlab.el +++ b/matlab.el @@ -1447,35 +1447,6 @@ Ignore comments and whitespace." ;;; Regexps for MATLAB language =============================================== -;; "-pre" means "partial regular expression" -;; "-if" and "-no-if" means "[no] Indent Function" - -(defconst matlab-mcos-innerblock-regexp "properties\\|methods\\|events\\|enumeration\\|arguments" - "Keywords which mark the beginning of mcos blocks. -These keywords can be overriden as variables or functions in other contexts -asside from that which they declare their content.") - -(defconst matlab-mcos-regexp (concat "\\|classdef\\|" matlab-mcos-innerblock-regexp) - "Keywords which mark the beginning of mcos blocks.") - -(defconst matlab-block-beg-pre-if - (concat "function\\|parfor\\|spmd\\|for\\|while\\|if\\|switch\\|try" - matlab-mcos-regexp) - "Keywords which mark the beginning of an indented block. -Includes function.") - -(defconst matlab-block-beg-pre-no-if - (concat "parfor\\|for\\|spmd\\|while\\|if\\|switch\\|try" - matlab-mcos-regexp) - "Keywords which mark the beginning of an indented block. -Excludes function.") - -(defun matlab-block-beg-pre () - "Partial regular expression to recognize MATLAB block-begin keywords." - (if matlab-functions-have-end - matlab-block-beg-pre-if - matlab-block-beg-pre-no-if)) - (defun matlab-match-function-re () "Expression to match a function start line. There are no reliable numeric matches in this expression. diff --git a/tests/complete.m b/tests/complete.m new file mode 100644 index 0000000..3fd5e80 --- /dev/null +++ b/tests/complete.m @@ -0,0 +1,47 @@ +function complete (a_arg1, b_arg2) +% This function is for testing completion tools + + arguments + a_arg1 (1,1) double + b_arg2 (2,1) int + end + + global a_global + global b_global + + a_localvar = 1; + b_localvar = 1; + + for a_for=1:10 + end + + if bool_var + end + + switch a_switch + end + + a_ + % @@(solo var "a_localvar" "a_switch" "a_for" "a_global" "a_arg1") + % @@(solo fcn ) + + % Note: For b, there are other test files the completion + % engine will find, so they are included. + b + % @@(solo fcn "blocal_fcn" "blazy_fcn" "buggy" "blocks") + % @@(solo var "b_localvar" "bool_var" "b_global" "b_arg2") + + + % quiet mlint + blocal_fcn(a_arg1, b_arg2, a_localvar, b_localvar, a_global, b_global); +end + +function blocal_fcn(varargin) + + blazy_fcn(varargin{:}); + +end + +function blazy_fcn(varargin) +end + diff --git a/tests/metest.el b/tests/metest.el index 5d6d9b5..b5570ad 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -34,6 +34,7 @@ (require 'matlab-load) (require 'matlab) (require 'cedet-matlab) +(require 'matlab-complete) (require 'semantic-matlab) ;; Enable semantic @@ -52,6 +53,7 @@ (setq-default matlab-indent-function-body 'MathWorks-Standard) ;; put it back (metest-run 'metest-comment-string-syntax-test) + (metest-run 'metest-fontlock-test) (metest-run 'metest-sexp-counting-test) (metest-run 'metest-sexp-traversal-test) @@ -61,10 +63,10 @@ (metest-indents-randomize-files) (metest-run 'metest-indents-test) + ;; Parsing and completion are high level tools (metest-run 'metest-parse-test) + (metest-run 'metest-complete-test) - (metest-run 'metest-fontlock-test) - (metest-log-report (metest-log-write)) (matlab-scan-stats-print) @@ -446,7 +448,52 @@ ( "df" . nil ) ) "List of testing keywords and associated faces.") - + + +(defvar met-complete-files '("complete.m") + "List of files for running font completion tests.") + +(defvar met-complete-tools '((var . matlab-find-recent-variable) + (fcn . matlab-find-user-functions) + ) + "List of tools that generate completions.") + +(defvar metest-complete-test (cons "completion" met-complete-files)) +(defun metest-complete-test (F) + "Test the completion tools in matlab-complete.el" + (let ((buf (metest-find-file F)) + exp act + (cnt 0)) + (with-current-buffer buf + (goto-char (point-min)) + + (while (re-search-forward "%\\s-*@@" nil t) + (setq exp (read (buffer-substring-no-properties (point) (scan-sexps (point) 1)))) + ;; Move to end of previous line, and try to do a complete + (matlab-with-context-line (matlab-previous-code-line (matlab-compute-line-context 2)) + (end-of-line) + (let* ((prefix (buffer-substring-no-properties + (save-excursion (forward-word -1) (point)) + (point))) + (sem (matlab-lattr-semantics prefix)) + ) + ;; Did we get the expected semantics of this location? + (when (not (eq sem (car exp))) + (metest-error "Completion Semantic Missmatch: Expected %s but found %s" (car exp) sem)) + + (let* ((expR (nthcdr 2 exp)) + (fcn (assoc (nth 1 exp) met-complete-tools)) + (act (funcall (cdr fcn) prefix))) + (when (not (equal act expR)) + (metest-error "Completion Missmatch: Expected %S but found %S using function %S" + expR act fcn)) + ) + )) + (setq cnt (1+ cnt)) + ;; Skip this match, find the next. + (end-of-line))) + (list cnt "tests"))) + (defvar met-fontlock-files '("fontlock.m" "mclass.m" "blocks.m") "List of files for running font lock tests.") From 8e9dd2e46f8b1f0c3b1d54c6e618e3bfbfabc8ac Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Mar 2021 14:52:35 -0400 Subject: [PATCH 134/168] matlab-scan.el: (matlab-line-comment-p, matlab-line-regular-comment-p, matlab-line-boring-code-p): Fix doc. (matlab-line-declaration-name): NEW matlab.el: (matlab-match-function-re, matlab-match-classdef-re): DELETE (matlab-mode-vf-functionname, matlab-mode-vf-classname): Re-write to use `matlab-line-declaration-name' instead of regex. --- matlab-scan.el | 54 +++++++++++++++++++++++++++++++++-- matlab.el | 76 ++++++++++++++------------------------------------ 2 files changed, 72 insertions(+), 58 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 7c258a4..29d38fc 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -346,11 +346,11 @@ Returns the value from the last part of forms." ;; Comments (defsubst matlab-line-comment-p (lvl1) - "Return t if the current line is a comment based on LVL1 cache." + "Return t if the current line is a comment." (eq (car lvl1) 'comment)) (defsubst matlab-line-regular-comment-p (lvl1) - "Return t if the current line is a boring style comment based on LVL1 cache." + "Return t if the current line is a comment w/ no attributes." (and (eq (car lvl1) 'comment) (eq (nth 1 lvl1) nil))) (defsubst matlab-line-comment-ignore-p (lvl1) @@ -398,7 +398,7 @@ All lines that start with a comment end with a comment." (eq (car lvl1) 'code)) (defsubst matlab-line-boring-code-p (lvl1) - "Return t if the current line is boring old code." + "Return t if the current line is code w/ no keyword." (and (eq (car lvl1) 'code) (not (nth 1 lvl1)))) (defsubst matlab-line-block-start-keyword-p (lvl1) @@ -462,6 +462,54 @@ Return nil for empty and comment only lines." (point) (save-excursion (skip-syntax-forward "w_") (point)))) ) +;; Declarations and Names +(defun matlab-line-declaration-name (&optional lvl1) + "Return string name of a declaration on the line. +For functions, this is the name of the function. +For classes, this is the name of the class. +Output is a list of the form: + ( NAME DECL-TYPE START END) +Where NAME is the name, and DECL-TYPE is one of +'function or 'class +START and END are buffer locations around the found name. +If the current line is not a declaration, return nil." + (unless lvl1 (setq lvl1 (matlab-compute-line-context 1))) + (when (matlab-line-declaration-p lvl1) + (let (type name start end) + (matlab-navigation-syntax + (matlab-with-context-line lvl1 + (cond + ;; FUNCTION : can have return arguments that need to be skipped. + ((string= (matlab-line-first-word-text lvl1) "function") + (forward-word 1) + (skip-syntax-forward " ") + (when (looking-at "\\(\\[[^]]+]\\|\\w+\\)\\s-*=") + (goto-char (match-end 0)) + (skip-syntax-forward " ")) + (setq type 'function + start (point) + end (save-excursion + (skip-syntax-forward "w_") + (point)))) + + ;; CLASS : Can have class attributes to be skipped. + ((string= (matlab-line-first-word-text lvl1) "classdef") + (forward-word 1) + (skip-syntax-forward " ") + (when (looking-at "([^)]+)") + (goto-char (match-end 0)) + (skip-syntax-forward " ")) + (setq type 'classdef + start (point) + end (save-excursion + (skip-syntax-forward "w_") + (point))))))) + + (when type + (list type (buffer-substring-no-properties start end) + start end)) + ))) + ;; Parenthetical blocks (defsubst matlab-line-close-paren-p (lvl1) "Non nil If the current line starts with closing paren (any type.)" diff --git a/matlab.el b/matlab.el index 3967176..68058f2 100644 --- a/matlab.el +++ b/matlab.el @@ -1444,23 +1444,6 @@ Ignore comments and whitespace." (forward-comment 100000) (not (eobp))) - -;;; Regexps for MATLAB language =============================================== - -(defun matlab-match-function-re () - "Expression to match a function start line. -There are no reliable numeric matches in this expression. -Know that `match-end' of 0 is the end of the function name." - ;; old function was too unstable. - ;;"\\(^function\\s-+\\)\\([^=\n]+=[ \t\n.]*\\)?\\(\\sw+\\)" - (concat "\\(^\\s-*function\\b[ \t\n.]*\\)\\(\\(\\[[^]]*\\]\\|\\sw+\\)" - "[ \t\n.]*=[ \t\n.]*\\|\\(\\)\\)\\(\\sw+\\)")) - -(defun matlab-match-classdef-re () - "Expression to match a classdef start line. -The class name is match 2." - "\\(^\\s-*classdef\\b[ \t\n]*\\)\\(\\sw+\\)\\(\\s-*<\\)?") - ;;; Navigation =============================================================== (defvar matlab-scan-on-screen-only nil @@ -1598,10 +1581,7 @@ a valid context." "Return the name of the current function." (save-excursion (matlab-beginning-of-defun) - (if (looking-at (matlab-match-function-re)) - (progn - (goto-char (match-end 0)) - (current-word))))) + (nth 1 (matlab-line-declaration-name)))) (defun matlab-beginning-of-command () "Go to the beginning of an M command. @@ -3044,26 +3024,16 @@ Optional argument FAST is ignored." (matlab-find-code-line) (let ((func nil) (bn (file-name-sans-extension - (file-name-nondirectory (buffer-file-name))))) - (if (looking-at (matlab-match-function-re)) - ;; The expression above creates too many numeric matches - ;; to apply a known one to our function. We cheat by knowing that - ;; match-end 0 is at the end of the function name. We can then go - ;; backwards, and get the extents we need. Navigation syntax - ;; lets us know that backward-word really covers the word. - (let ((end (match-end 0)) - (begin (progn (goto-char (match-end 0)) - (forward-word -1) - (point)))) - (setq func (buffer-substring-no-properties begin end)) - (if (not (string= func bn)) - (if (not (matlab-mode-highlight-ask - begin end - "Function and file names are different. Fix function name?")) - nil - (goto-char begin) - (delete-region begin end) - (insert bn)))))))) + (file-name-nondirectory (buffer-file-name)))) + (fcn (matlab-line-declaration-name))) + (when (and fcn (eq (car fcn) 'function) + (not (string= (nth 1 fcn) bn))) + (unless (not (matlab-mode-highlight-ask + (nth 2 fcn) (nth 3 fcn) + "Function name and file names are different. Fix function name?")) + (goto-char (nth 2 fcn)) + (delete-region (nth 2 fcn) (nth 3 fcn)) + (insert bn)))))) (defun matlab-mode-vf-classname (&optional fast) "Verify/Fix the class name of this file. @@ -3073,20 +3043,16 @@ Optional argument FAST is ignored." (matlab-find-code-line) (let ((class nil) (bn (file-name-sans-extension - (file-name-nondirectory (buffer-file-name))))) - (if (looking-at (matlab-match-classdef-re)) - ;; The name of this class is match 2. - (let ((end (match-end 2)) - (begin (match-beginning 2))) - (setq class (buffer-substring-no-properties begin end)) - (if (not (string= class bn)) - (if (not (matlab-mode-highlight-ask - begin end - "Class name and file names are different. Fix class name?")) - nil - (goto-char begin) - (delete-region begin end) - (insert bn)))))))) + (file-name-nondirectory (buffer-file-name)))) + (class (matlab-line-declaration-name))) + (when (and class (eq (car class) 'classdef) + (not (string= (nth 1 class) bn))) + (unless (not (matlab-mode-highlight-ask + (nth 2 class) (nth 3 class) + "Class name and file names are different. Fix class name?")) + (goto-char (nth 2 class)) + (delete-region (nth 2 class) (nth 3 class)) + (insert bn)))))) (defun matlab-mode-vf-add-ends (&optional fast) "Verify/Fix adding ENDS to functions. From 8a133c47b27fda2fcea18182c12df1a4b0676ccf Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 27 Mar 2021 19:59:02 -0400 Subject: [PATCH 135/168] matlab.el: (matlab-mode): Make show-paren-data-function buffer local before setting it. --- matlab.el | 1 + 1 file changed, 1 insertion(+) diff --git a/matlab.el b/matlab.el index 68058f2..78b2999 100644 --- a/matlab.el +++ b/matlab.el @@ -1356,6 +1356,7 @@ All Key Bindings: (setq font-lock-multiline 'undecided) (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) + (make-local-variable 'show-paren-data-function) (setq show-paren-data-function 'matlab-show-paren-or-block) ;; If first function is terminated with an end statement, then functions have From 7c41d9a15108d2a65b9fd05acc200733663ac2ba Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 28 Mar 2021 10:22:40 -0400 Subject: [PATCH 136/168] matlab-scan.el: (mlf-previous-line*): Deleted, renumber the rest. (matlab-compute-line-context-lvl2): Stop tracking prev line1/2, these weren't being used. (matlab-previous-line, matlb-previous-line-lvl2): These weren't used. (matlab-line-in-array): Deleted - replaced the few uses. matlab.el: (matlab--previous-line-indent-recommendation): Replace matlab-line-in-array with it's 1 line implementation. Remove `matlab-previous-line' which could never have a non-nil value. (matlab-show-line-info): Pass nil into matlab-next-line-indentation since it will compute as needed, and old fcn always returned nil anyway. --- matlab-scan.el | 67 +++++++------------------------------------------- matlab.el | 7 +++--- 2 files changed, 12 insertions(+), 62 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 29d38fc..78f073e 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -548,13 +548,11 @@ If the current line is not a declaration, return nil." ;; Level 2 scanning information cascades from line-to-line, several fields will ;; be blank unless a previous line is also scanned. (defconst mlf-level1 0) -(defconst mlf-previous-line1 1) -(defconst mlf-previous-line2 2) -(defconst mlf-previous-command-beginning 3) -(defconst mlf-previous-nonempty 4) -(defconst mlf-previous-code 5) -(defconst mlf-previous-block 6) -(defconst mlf-previous-fcn 7) +(defconst mlf-previous-command-beginning 1) +(defconst mlf-previous-nonempty 2) +(defconst mlf-previous-code 3) +(defconst mlf-previous-block 4) +(defconst mlf-previous-fcn 5) (defun matlab-compute-line-context-lvl-2 (&optional lvl1 previous2) "Compute the level 2 context for the current line of MATLAB code. @@ -593,8 +591,8 @@ in the returned list for quick access." ;; copy data from previous2. (if previous2 (progn - (setq prev-lvl1 (car previous2) - prev-lvl2 previous2 + (setq prev-lvl1 t + prev-lvl2 t prev-cmd-begin (nth mlf-previous-command-beginning previous2) prev-nonempty (nth mlf-previous-nonempty previous2) prev-code1 (nth mlf-previous-code previous2) @@ -655,7 +653,7 @@ in the returned list for quick access." ;; Do something with comment here ?? ))) - (list lvl1 prev-lvl1 prev-lvl2 prev-cmd-begin prev-nonempty prev-code1 prev-block1 prev-fcn1 + (list lvl1 prev-cmd-begin prev-nonempty prev-code1 prev-block1 prev-fcn1 )))) @@ -685,24 +683,6 @@ If LVL2 is nil, compute it." (if (consp (car lvl2)) (car lvl2) lvl2) (matlab-compute-line-context 1))) -(defun matlab-previous-line (lvl2) - "Return the previous line from lvl2 context." - (matlab-scan-stat-inc 'prevline) - (nth mlf-previous-line1 lvl2)) - -(defun matlab-previous-line-lvl2 (lvl2) - "Return the previous line from lvl2 context." - (let ((prev (nth mlf-previous-line2 lvl2))) - (if (eq prev t) - (save-excursion - (matlab-scan-stat-inc 'prevline2miss) - (matlab-with-context-line (matlab-previous-line lvl2) - (setq prev (matlab-compute-line-context 2))) - (setcar (nthcdr mlf-previous-line1 lvl2) prev)) - ;; Else, return - (matlab-scan-stat-inc 'prevline2) - prev))) - (defun matlab-previous-nonempty-line (lvl2) "Return lvl1 ctxt for previous non-empty line." (let ((prev (nth mlf-previous-nonempty lvl2)) @@ -749,12 +729,8 @@ If LVL2 is nil, compute it." (if (eq prev t) ;; Compute it and stash it. (save-excursion - (matlab-scan-stat-inc 'cmdbeginmiss) - (beginning-of-line) - (when (not (bobp)) - (forward-comment -100000) ;; Skip over all whitespace and comments. + (matlab-with-context-line (matlab-previous-code-line lvl2) (matlab-scan-beginning-of-command) - ;; TODO!! (setq prev (matlab-compute-line-context 1)) (setcar (nthcdr mlf-previous-command-beginning lvl2) prev))) ;; else record a cache hit @@ -762,31 +738,6 @@ If LVL2 is nil, compute it." ) prev)) -;;(defun matlab-previous-fcn-line (lvl2) -;; "Return lvl1 ctxt for previous non-empty line." -;; (let ((prev (nth mlf-previous-fcn lvl2)) -;; ) -;; (if (eq prev t) -;; ;; Compute it and stash it. -;; (save-excursion -;; (matlab-scan-stat-inc 'fcnmiss) -;; (beginning-of-line) -;; ;; TODO -;; (setq prev (matlab-compute-line-context 1)) -;; (setcar (nthcdr mlf-previous-fcn lvl2) prev)) -;; ;; else record a cache hit -;; (matlab-scan-stat-inc 'fcn) -;; ) -;; prev)) - - - -;;; MATLAB focused queries (more specific names than for first set) -;; -(defun matlab-line-in-array (lvl2) - "Return the location of an opening paren if in array parens." - (matlab-line-close-paren-outer-point (matlab-get-lvl1-from-lvl2 lvl2))) - ;;; Scanning Accessor utilities ;; diff --git a/matlab.el b/matlab.el index 78b2999..cdb4547 100644 --- a/matlab.el +++ b/matlab.el @@ -1791,7 +1791,7 @@ line of code. It then scans that line and recommends either: ((save-excursion (beginning-of-line) (bobp)) ;; Beginning of buffer - do no work, just return 0. 0) - ((matlab-line-in-array lvl2) + ((matlab-line-close-paren-outer-point (matlab-get-lvl1-from-lvl2 lvl2)) ;; If we are inside an array continuation, then we shouldn't ;; need to do anything complicated here b/c we'll just ignore ;; the returned value in the next step. Return current indentation @@ -1800,8 +1800,7 @@ line of code. It then scans that line and recommends either: (t ;; Else, the previous line might recommend an indentation based ;; on it's own context, like being a block open or continuation. - (let ((prevcmd (or (matlab-previous-code-line lvl2) - (matlab-previous-line lvl2)))) + (let ((prevcmd (matlab-previous-code-line lvl2))) (matlab-with-context-line prevcmd (matlab-next-line-indentation prevcmd)))))) @@ -3210,7 +3209,7 @@ desired. Optional argument FAST is not used." (lvl1msg (matlab-describe-line-indent-context lvl1 t)) (indent nil) (fullindent (matlab--calc-indent lvl2 'indent)) - (nexti (matlab-next-line-indentation (matlab-previous-line lvl2)))) + (nexti (matlab-next-line-indentation nil))) (setq msg (concat msg "Line Syntax: " lvl1msg " | Preferred Indents: This: " (int-to-string (nth 1 indent)) From 54b327976296d45ef6285f77f6dfeb3428202c81 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 11:07:16 -0400 Subject: [PATCH 137/168] matlab-shell-gud.el: (matlab-shell-mode-gud-enable-bindings): Remove call to `matlab-frame-init' which is obsolete. --- matlab-shell-gud.el | 1 - 1 file changed, 1 deletion(-) diff --git a/matlab-shell-gud.el b/matlab-shell-gud.el index 68ab926..9d27fa5 100644 --- a/matlab-shell-gud.el +++ b/matlab-shell-gud.el @@ -89,7 +89,6 @@ Disable this option if the tooltips are too slow in your setup." ;; (gud-def gud-print "% gud-print not available" "\C-p" "gud-print not available.") (when window-system - (matlab-frame-init) (setq gud-matlab-tool-bar-map (let ((map (make-sparse-keymap))) From 7082d4f2bfe1f01347f6ef44b5fa6539ff6816f3 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 11:07:44 -0400 Subject: [PATCH 138/168] toolbox/emacsrun.m: Convert input 'args' to 'varargin' so they can be optional. --- toolbox/emacsrun.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toolbox/emacsrun.m b/toolbox/emacsrun.m index 1c1f18b..79e7ae2 100644 --- a/toolbox/emacsrun.m +++ b/toolbox/emacsrun.m @@ -1,4 +1,4 @@ -function emacsrun(mfile, args) +function emacsrun(mfile, varargin) % Run code from MFILE. % Assumes MFILE was recently edited, and proactively clears that function. % @@ -22,7 +22,7 @@ function emacsrun(mfile, args) end clear(shortFileName); - cmd = [ shortFileName args ]; + cmd = [ shortFileName varargin{:} ]; evalin('base',cmd); end \ No newline at end of file From 9c5e34d633ee5fe87be9d21e1c7df3db247dea36 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 12:48:38 -0400 Subject: [PATCH 139/168] matlab-shell-gud.el: (mlg-set-stack, mlg-add-breakpoint): Use make-instance to create instances of objects. --- matlab-shell-gud.el | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/matlab-shell-gud.el b/matlab-shell-gud.el index 9d27fa5..a532220 100644 --- a/matlab-shell-gud.el +++ b/matlab-shell-gud.el @@ -372,10 +372,10 @@ LONGESTNAME specifies the how long the longest name we can expect is." "Specify a NEWSTACK provided by MATLAB to replace the old one." (setq mlg-stack nil) (dolist (L newstack) - (push (mlg-stack-frame "" - :file (nth 0 L) - :name (nth 1 L) - :line (nth 2 L)) + (push (make-instance 'mlg-stack-frame + :file (nth 0 L) + :name (nth 1 L) + :line (nth 2 L)) mlg-stack)) (setq mlg-stack (nreverse mlg-stack)) (mlg-refresh-stack-buffer) @@ -597,9 +597,10 @@ LONGESTNAME specifies the how long the longest name we can expect is." (setq found t))) (when (not found) (setq matlab-gud-visible-breakpoints - (cons (mlg-breakpoint "" :file file - :name fcn - :line line) + (cons (make-instance 'mlg-breakpoint + :file file + :name fcn + :line line) matlab-gud-visible-breakpoints)) (mlg-activate (car matlab-gud-visible-breakpoints)) )) From ed4dbaf02ec12946f9ec0e465498b713211b91d4 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 20:37:04 -0400 Subject: [PATCH 140/168] mlint.el: (mlint-lm-replace-focus::initialize-instance) (mlint-lm-function-name::initialize-instance) (mlint-lm-entry-deprecated::initialize-instance): Quote in the class name when passed to oref-default to silence warning. semanticdb-matlab.el: (semanticdb-project-system-databases) (semanticdb-project-database-matlab::semanticdb-get-database-tables) Use 'make-instance' to create instances of classes. --- mlint.el | 6 +++--- semanticdb-matlab.el | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mlint.el b/mlint.el index 4516022..3397cd3 100644 --- a/mlint.el +++ b/mlint.el @@ -518,7 +518,7 @@ Subclasses fulfill the duty of actually fixing the code." Optional argument FIELDS are the initialization arguments." ;; After basic initialization, update the fix description. (oset this fix-description - (concat (oref-default mlint-lm-replace-focus fix-description) + (concat (oref-default this fix-description) (oref this new-text)))) (cl-defmethod mlint-fix-entry ((ent mlint-lm-replace-focus)) @@ -548,7 +548,7 @@ Optional argument FIELDS are the initialization arguments." (oset this new-text newfcn) ;; After basic initialization, update the fix description. (oset this fix-description - (concat (oref-default mlint-lm-replace-focus fix-description) + (concat (oref-default this fix-description) newfcn)) )) @@ -568,7 +568,7 @@ Optional arguments FIELDS are the initialization arguments." (oset this new-text newfcn) ;; After basic initialization, update the fix description. (oset this fix-description - (concat (oref-default mlint-lm-replace-focus fix-description) + (concat (oref-default this fix-description) newfcn)) )) diff --git a/semanticdb-matlab.el b/semanticdb-matlab.el index ec56fd0..1d51a1a 100644 --- a/semanticdb-matlab.el +++ b/semanticdb-matlab.el @@ -76,7 +76,7 @@ ;; Create the database, and add it to searchable databases for matlab mode. (defvar-mode-local matlab-mode semanticdb-project-system-databases (list - (semanticdb-project-database-matlab "Matlab")) + (make-instance 'semanticdb-project-database-matlab)) "Search MATLAB path for symbols.") ;; NOTE: Be sure to modify this to the best advantage of your @@ -99,7 +99,7 @@ Create one of our special tables that can act as an intermediary." ;; We need to return something since there is always the "master table" ;; The table can then answer file name type questions. (when (not (slot-boundp obj 'tables)) - (let ((newtable (semanticdb-table-matlab "MATLAB system table"))) + (let ((newtable (make-instance 'semanticdb-table-matlab))) (oset obj tables (list newtable)) (oset newtable parent-db obj) (oset newtable tags nil) From 3b2123654674ba0a5116a581b1e3eb2c5e08cd13 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 20:52:15 -0400 Subject: [PATCH 141/168] matlab-shell.el: (matlab-shell-font-lock-keywords, matlab-shell-object-output-font-lock-keywords): Move a few extra things out of base keywords and into next level keywords. --- matlab-shell.el | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/matlab-shell.el b/matlab-shell.el index ef638fc..cb64bfe 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -206,17 +206,6 @@ If multiple prompts are seen together, only call this once.") ;; Extra font lock keywords for the MATLAB shell. (defconst matlab-shell-font-lock-keywords (list - ;; Startup notices - '(" M A T L A B " 0 'underline) - '("All Rights Reserved" 0 'italic) - '("\\(\\(?:(c)\\)?\\s-+Copyright[^\n]+\\)" 1 font-lock-comment-face) - '("\\(Version\\)\\s-+\\([^\n]+\\)" - (1 font-lock-function-name-face) (2 font-lock-variable-name-face)) - '("\\(R[0-9]+[ab]\\(?: Update [0-9]+\\)\\) \\([^\n]+\\)" - (1 font-lock-function-name-face) (2 font-lock-variable-name-face)) - '("^To get started, type doc.$" 0 font-lock-comment-face prepend) - '("For product information, [^\n]+" 0 font-lock-comment-face) - ;; How about Errors? '("^\\(Error in\\|Syntax error in\\)\\s-+==>\\s-+\\(.+\\)$" (1 font-lock-comment-face) (2 font-lock-string-face)) @@ -224,10 +213,6 @@ If multiple prompts are seen together, only call this once.") '("^\\(\\(On \\)?line [0-9]+\\)" 1 font-lock-comment-face) ;; User beep things '("\\(\\?\\?\\?[^\n]+\\)" 1 font-lock-comment-face) - ;; Useful user commands, but not useful programming constructs - '("\\<\\(demo\\|whatsnew\\|info\\|subscribe\\|help\\|doc\\|lookfor\\|what\ -\\|whos?\\|cd\\|clear\\|load\\|save\\|helpdesk\\|helpwin\\)\\>" - 1 font-lock-keyword-face) ) "Additional keywords used by MATLAB when reporting errors in interactive\ mode.") @@ -239,6 +224,21 @@ mode.") (defconst matlab-shell-object-output-font-lock-keywords (list + ;; Startup notices + '(" M A T L A B " 0 'underline) + '("All Rights Reserved" 0 'italic) + '("\\(\\(?:(c)\\)?\\s-+Copyright[^\n]+\\)" 1 font-lock-comment-face) + '("\\(Version\\)\\s-+\\([^\n]+\\)" + (1 font-lock-function-name-face) (2 font-lock-variable-name-face)) + '("\\(R[0-9]+[ab]\\(?: Update [0-9]+\\)\\) \\([^\n]+\\)" + (1 font-lock-function-name-face) (2 font-lock-variable-name-face)) + '("^To get started, type doc.$" 0 font-lock-comment-face prepend) + '("For product information, [^\n]+" 0 font-lock-comment-face) + + ;; Useful user commands, but not useful programming constructs + '("\\<\\(demo\\|whatsnew\\|info\\|subscribe\\|help\\|doc\\|lookfor\\|what\ +\\|whos?\\|cd\\|clear\\|load\\|save\\|helpdesk\\|helpwin\\)\\>" + 1 font-lock-keyword-face) ;; disp of objects usually looks like this: '("^\\s-*\\(\\w+\\) with properties:" (1 font-lock-type-face)) ;; object output - highlight property names after 'with properties:' indicator From b01dd6d63d039f8f64ff9323d86df5798686e0fa Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 22:14:20 -0400 Subject: [PATCH 142/168] matlab.el: (matlab-calculate-indentation): Break continuations into 2 bins: Those from parens, and those not. Isolate non-paren version to avoid calling 'beginning-of-command'. Instead just look at previous line, and decide if we need to do a continuation indent or not, otherwise just follow the previous line. --- matlab.el | 79 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/matlab.el b/matlab.el index cdb4547..b7a03d1 100644 --- a/matlab.el +++ b/matlab.el @@ -1950,8 +1950,8 @@ this line." ;; Code always matches up against the previous line. (list 'code (matlab--previous-line-indent-recommendation lvl2))) - ;; CONTINUATION : A group of cases for continuation - (t + ;; CONTINUATION but from within a parenthetical: A group of cases for continuation + ((matlab-line-close-paren-inner-col lvl1) (let* ((boc-lvl1 (save-excursion (matlab-scan-beginning-of-command) (matlab-compute-line-context 1))) @@ -1969,12 +1969,10 @@ this line." ;; What shall we use to describe this for debugging? - (indent-type (cond ((matlab-line-empty-p lvl1) 'empty) - ((and parenchar (= parenchar ?\()) 'function-call-cont) + (indent-type (cond ((and parenchar (= parenchar ?\()) 'function-call-cont) ((and parencol (= parenindent parencol)) 'array-solo-cont) ((and parenpt (/= parenpt parenopt)) 'nested-array-cont) - (parenpt 'array-cont) - (t 'code-cont))) + (t 'code-cont))) ;; last not likely. (found-column nil) ) @@ -2010,8 +2008,8 @@ this line." (setq tmp (+ parencol matlab-arg1-max-indent-length)) (setq tmp (current-column)))) - ;; CONTINUATION with PARENS - (parenchar ;; a str if in parens + ;; CONTINUATION with PARENS, BRACKETS, etc + (t (let* ((mi (assoc parenchar matlab-maximum-indents)) (max (if mi (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)) nil)) (ind (if mi (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)) nil))) @@ -2044,21 +2042,45 @@ this line." ;; (+ ci-boc ind) ;; (current-column)) ))) + )) + (list indent-type tmp))) - ;; CONTINUATION with EQUALS - ((save-excursion - (goto-char boc) - (while (and (re-search-forward "=" (matlab-point-at-eol) t) - (matlab-cursor-in-string-or-comment))) - (when (= (preceding-char) ?=) - (skip-chars-forward " \t") - (setq found-column (point))) - ) + (t + ;; Other kinds of continuations + (let* ((prev-lvl1 (save-excursion + (forward-line -1) + (matlab-compute-line-context 1))) + (prev2-lvl1 (save-excursion + (forward-line -2) + (matlab-compute-line-context 1))) + (ci-prev (matlab-line-indentation prev-lvl1)) + + (boc (matlab-line-point prev-lvl1)) + (boc2 (matlab-line-point prev2-lvl1)) + + (indent-type (cond ((matlab-line-empty-p lvl1) 'empty) + (t 'code-cont))) + (found-column nil) + ) + + (save-excursion + (cond + ;; Beginning of CONTINUATION has EQUALS + ((and (or (not (matlab-line-ellipsis-p prev2-lvl1)) + (= boc boc2)) + (save-excursion + (goto-char boc) + (while (and (re-search-forward "=" (matlab-point-at-eol) t) + (matlab-cursor-in-string-or-comment))) + (when (= (preceding-char) ?=) + (skip-chars-forward " \t") + (setq found-column (point))) + )) (save-excursion (goto-char found-column) (let ((cc (current-column)) (mi (assoc ?= matlab-maximum-indents))) - + (if (looking-at "\\.\\.\\.\\|$") ;; In this case, the user obviously wants the ;; indentation to be somewhere else. @@ -2066,16 +2088,25 @@ this line." ;; If the indent delta is greater than the max, ;; use the max + current (if (and mi (> (- cc (matlab--previous-line-indent-recommendation lvl2)) (if (listp (cdr mi)) - (car (cdr mi)) - (cdr mi)))) + (car (cdr mi)) + (cdr mi)))) (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) (if (listp (cdr mi)) - (cdr (cdr mi)) - (cdr mi)))) + (cdr (cdr mi)) + (cdr mi)))) (setq tmp cc)))))) - + ;; CONTINUATION with nothing special about it. + ((or (not (matlab-line-ellipsis-p prev2-lvl1)) + (= boc boc2)) + + ;; Continued from non-continued line, push in just a little + (setq tmp (+ ci-prev matlab-continuation-indent-level))) + + ;; CONTINUATION from a continued line, nothing special (t - (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) matlab-continuation-indent-level))) + ;; Just match the same + (setq tmp ci-prev)) + )) (list indent-type tmp) From e6e362a5a4d5bcbc3ff8f1cf48b2d0ed15d36d8b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Tue, 30 Mar 2021 22:17:39 -0400 Subject: [PATCH 143/168] tests/indents.m, tests/mclass_cont.m: Change indentation expecatations of some lines to match bug fix. --- tests/indents.m | 4 ++-- tests/mclass_cont.m | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/indents.m b/tests/indents.m index 9bb9c51..df3b736 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -303,8 +303,8 @@ function B = continuations_and_block_comments % !!4 - more comments if condition1 || ... % !!4 - fcn_call(arg1, ... % !!12 - arg2) % !!21 + fcn_call(arg1, ... % !!8 + arg2) % !!17 line_in_if(); end % !!4 diff --git a/tests/mclass_cont.m b/tests/mclass_cont.m index ee0a0cb..5b442cc 100644 --- a/tests/mclass_cont.m +++ b/tests/mclass_cont.m @@ -1,5 +1,5 @@ classdef mclass_cont < otherThing & ... % !!0 This continuation can case issue for next prop block - handle %!!8 + handle %!!4 properties (Dependent, SetAccess = private) %!!4 TopicName %!!8 From 41f32067339da1bf377a7887ff3e4f1d5285e918 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 31 Mar 2021 13:07:11 -0400 Subject: [PATCH 144/168] matlab.el: (matlab-calculate-indentation): For continued from previous non-continued line, no arrays: Include indent recommendation from the previous line to account for indentation under ctrl blocks. (matlab-next-line-indentation): While computing startpt, be sure to move pt to the line that was passed in. --- matlab.el | 9 ++++++--- tests/indents.m | 4 ++-- tests/mclass_cont.m | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/matlab.el b/matlab.el index b7a03d1..e71944b 100644 --- a/matlab.el +++ b/matlab.el @@ -2100,7 +2100,9 @@ this line." (= boc boc2)) ;; Continued from non-continued line, push in just a little - (setq tmp (+ ci-prev matlab-continuation-indent-level))) + ;; Do explicit call to next-line-indentation b/c we've already computed the lvl1 context + ;; on the beginning of command. + (setq tmp (+ (matlab-next-line-indentation prev-lvl1) matlab-continuation-indent-level))) ;; CONTINUATION from a continued line, nothing special (t @@ -2116,11 +2118,12 @@ this line." (defun matlab-next-line-indentation (lvl1) "Calculate the indentation for lines following this command line. See `matlab-calculate-indentation' for how the output of this fcn is used." - (let ((startpnt (point-at-eol)) + (let ((startpnt (matlab-with-context-line lvl1 + (point-at-eol))) ) (save-excursion (matlab-scan-beginning-of-command lvl1) - + (back-to-indentation) (setq lvl1 (matlab-compute-line-context 1)) (let ((cc (matlab-line-count-closed-blocks startpnt)) diff --git a/tests/indents.m b/tests/indents.m index df3b736..9bb9c51 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -303,8 +303,8 @@ function B = continuations_and_block_comments % !!4 - more comments if condition1 || ... % !!4 - fcn_call(arg1, ... % !!8 - arg2) % !!17 + fcn_call(arg1, ... % !!12 + arg2) % !!21 line_in_if(); end % !!4 diff --git a/tests/mclass_cont.m b/tests/mclass_cont.m index 5b442cc..ee0a0cb 100644 --- a/tests/mclass_cont.m +++ b/tests/mclass_cont.m @@ -1,5 +1,5 @@ classdef mclass_cont < otherThing & ... % !!0 This continuation can case issue for next prop block - handle %!!4 + handle %!!8 properties (Dependent, SetAccess = private) %!!4 TopicName %!!8 From 32aef8ebc11f84a1b5159745899a9d9a43d8711b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Wed, 31 Mar 2021 20:51:55 -0400 Subject: [PATCH 145/168] matlab.el: (matlab-functions-have-end-minor-mode): When activating this mode for classes, now disable command-dual support. Speeds up indenting very large classes. --- matlab.el | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/matlab.el b/matlab.el index e71944b..fdbcd40 100644 --- a/matlab.el +++ b/matlab.el @@ -216,8 +216,21 @@ If the value is 'guess, then we guess if a file has end when (setq matlab-functions-have-end 'guess) (setq matlab-functions-have-end type)) (setq matlab-functions-have-end nil) - )) - ) + ) + ;; Depending on the kind of end, lets set other variables. + (cond ((eq matlab-functions-have-end 'guess) + ;;(setq matlab-syntax-support-command-dual t) + ) + ((eq matlab-functions-have-end 'class) + (setq matlab-syntax-support-command-dual nil) + ) + (matlab-functions-have-end + ;;(setq matlab-syntax-support-command-dual t) + ) + (t + ;;(setq matlab-syntax-support-command-dual nil) + )) + )) (defvar matlab-last-script-type-guess nil "The last time we guessed the script type, what was it?") From b8343915f983ec829abefcade9d8b33088213621 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 1 Apr 2021 09:14:07 -0400 Subject: [PATCH 146/168] matlab-scan.el: (matlab-compute-line-context-lvl-1): Optimize use of `syntax-ppss'. See big comment for details on how it works. matlab.el: (matlab-functions-have-end-minor-mode): Re-enable command-line-dual checking in classes. --- matlab-scan.el | 34 +++++++++++++++++++++++++++++++--- matlab.el | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 78f073e..6002c2d 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -194,9 +194,37 @@ and other simple states. Computes multiple styles of line by checking for multiple types of context in a single call using fastest methods." (save-excursion - (back-to-indentation) - (let* ((ppsend (save-excursion (syntax-ppss (point-at-eol)))) - (pps (syntax-ppss (point))) + ;; About use of `syntax-ppss' - This util has a 1 element cache, + ;; and can utilize the cache as it parses FORWARD only. Tools + ;; like `back-to-indentation' also needs to propertize the buffer + ;; since that uses syntax elements to do it's work. To make this + ;; fcn faster, we call `syntax-ppss' once on BOL, and then + ;; parse-partial-sexp for other locations as a way to boost the + ;; speed of calls to `syntax-ppss' that come later. + + ;; Additional note: `back-to-indentation' used below calls + ;; syntax-propertize. Command dual needs to call + ;; `syntax-ppss' which it does on bol By setting `syntax-ppss' + ;; internal cache on bol, in cases where propertize needs to + ;; be called, the cache returns immediatly during the + ;; propertize, and needs to do no extra work. + (beginning-of-line) + (let* ((ppsbol (syntax-ppss (point))) ;; Use the cache + (pps (progn (back-to-indentation) + ;; Compute by hand - leaving cache alone. + (parse-partial-sexp (point-at-bol) + (point) + nil nil + ;; previous state + ppsbol))) + (ppsend (save-excursion + ;; Starting @ indentation, parse forward to eol + ;; and see where we are. + ;; Compute by hand, leaving cache alone. + (parse-partial-sexp (point) (point-at-eol) + nil nil + ;; Previous state + pps))) (ltype 'empty) (stype nil) (pt (point)) diff --git a/matlab.el b/matlab.el index fdbcd40..4cbe027 100644 --- a/matlab.el +++ b/matlab.el @@ -222,7 +222,7 @@ If the value is 'guess, then we guess if a file has end when ;;(setq matlab-syntax-support-command-dual t) ) ((eq matlab-functions-have-end 'class) - (setq matlab-syntax-support-command-dual nil) + ;;(setq matlab-syntax-support-command-dual nil) ) (matlab-functions-have-end ;;(setq matlab-syntax-support-command-dual t) From f4c6a803fb3ca1e86f09eab6c4ee03bfd95fb175 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 1 Apr 2021 12:51:58 -0400 Subject: [PATCH 147/168] matlab-scan.el: (matlab-scan-previous-line-ellipsis-p): Use `parse-partial-sexp' instead of `syntax-ppss' b/c for ellipsis, we only need to check from bol, and parens and comments can be ignored. matlab.el: (matlab-line-count-open-blocks, matlab-line-count-closed-blocks): Update to accept 2 lvl1 context for the start and end of the block searches. Use these to bound the search, and avoid creating more contexts, and avoid calling `matlab-scan-beginning-of-command'. (matlab-next-line-indentation): Be careful about calls that require syntax to propertize the buffer. Starting with input lvl1, capture lvl1 ctxt for beginning of command and use that for remaining calls. This makes it more explicity about what is being checked where, and faster. --- matlab-scan.el | 6 +++++- matlab.el | 55 +++++++++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 6002c2d..f3d59bb 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -804,7 +804,11 @@ This is true iff the previous line has an ellipsis." (beginning-of-line) (when (not (bobp)) (forward-char -1) - (let* ((pps (syntax-ppss (point))) + ;; Ellipsis scan always resets at BOL, so call non-cached + ;; `parse-partial-sexp' instead of regular `syntax-ppss' which + ;; can be slow as it attempts to get a solid start from someplace + ;; potentially far away. + (let* ((pps (parse-partial-sexp (point-at-bol) (point-at-eol))) ;;(syntax-ppss (point))) (csc (nth 8 pps))) ;; Ellipsis start has a syntax of 11 (comment-start). ;; Other comments have high-bit flags, so don't == 11. diff --git a/matlab.el b/matlab.el index 4cbe027..a8c8fbf 100644 --- a/matlab.el +++ b/matlab.el @@ -1625,21 +1625,21 @@ Travells a cross continuations" (forward-line -1) (matlab-line-end-comment-column (matlab-compute-line-context 1)))))) -(defun matlab-line-count-open-blocks (&optional eol) +(defun matlab-line-count-open-blocks (lvl1 end-lvl1) "Return a the number of unterminated block constructs. This is any block, such as if or for, that doesn't have an END on this line. -Optional EOL indicates a virtual end of line." - (save-excursion +Input LVL1 indicates the line we are searching. +Input END-LVL1 indicates the line whose end we search until." + (matlab-with-context-line lvl1 (let* ((v 0) - (lvl (matlab-compute-line-context 1)) - (bound (or eol (save-excursion (matlab-line-end-of-code lvl)))) + (bound (matlab-line-end-of-code end-lvl1)) (keyword nil)) - (if (or (matlab-line-comment-p lvl) - (matlab-line-empty-p lvl)) + (if (or (matlab-line-comment-p lvl1) + (matlab-line-empty-p lvl1)) ;; If this line is comments or empty, no code to scan 0 - (back-to-indentation) + (goto-char (matlab-line-point lvl1)) (while (setq keyword (matlab-re-search-keyword-forward (matlab-keyword-regex 'indent) bound t)) (if (or (and (eq (car keyword) 'mcos) @@ -1657,14 +1657,15 @@ Optional EOL indicates a virtual end of line." (setq v (1- v)))))) v)))) -(defun matlab-line-count-closed-blocks (&optional start) +(defun matlab-line-count-closed-blocks (lvl1 bound-lvl1) "Return the number of closing block constructs on this line. -Argument START is where to start searching from." - (save-excursion - (when start (goto-char start)) +Argument LVL1 is the line we'll start on (at the end). +Argument BOUND-LVL1 is where to stop search backwards from." + (matlab-with-context-line lvl1 (let ((v 0) - (lvl1 (matlab-compute-line-context 1)) - (bound (save-excursion (matlab-scan-beginning-of-command)))) + ;;(lvl1 (matlab-compute-line-context 1)) + (bound (matlab-line-point bound-lvl1)) + ) (if (matlab-line-comment-p lvl1) ;; If this is even vagely a comment line, then there is no @@ -1672,20 +1673,20 @@ Argument START is where to start searching from." 0 ;; Else, lets scan. ;; lets only scan from the beginning of the comment - (goto-char start) (matlab-line-end-of-code lvl1) ;; Count every END from our starting point till the beginning of the ;; command. That count indicates unindent from the beginning of the ;; command which anchors the starting indent. + (goto-char (matlab-line-end-of-code lvl1)) (while (matlab-re-search-keyword-backward (matlab-keyword-regex 'end) bound t) (let ((startmove (match-end 0)) (nomove (point))) ;; Lets count these end constructs. (setq v (1+ v)) - (if (matlab--scan-block-backward bound) ;;(matlab-backward-sexp t t) + (if (matlab--scan-block-backward bound) (goto-char nomove) - (setq v (1- v))) + (setq v (1- v))) ;; If whole block, uncount the end )) (if (<= v 0) 0 v))))) @@ -2137,21 +2138,21 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (save-excursion (matlab-scan-beginning-of-command lvl1) - (back-to-indentation) - (setq lvl1 (matlab-compute-line-context 1)) - (let ((cc (matlab-line-count-closed-blocks startpnt)) - (bc (matlab-line-count-open-blocks startpnt)) - (end (matlab-line-end-p lvl1)) - (mc (and (matlab-line-block-middle-p lvl1) 1)) - (ec (and (matlab-line-block-case-p lvl1) 1)) - (ci (current-indentation))) + ;;(back-to-indentation) + (let* ((boc-lvl1 (matlab-compute-line-context 1)) + (cc (matlab-line-count-closed-blocks lvl1 boc-lvl1)) + (bc (matlab-line-count-open-blocks boc-lvl1 lvl1)) + (end (matlab-line-end-p boc-lvl1)) + (mc (and (matlab-line-block-middle-p lvl1) 1)) + (ec (and (matlab-line-block-case-p lvl1) 1)) + (ci (current-indentation))) ;; When CC is positive, and END is false, or CC > 1, then the NEXT ;; line should have an indent that matches the context at the beginning ;; of the block of the last end. (if (or (> cc 1) (and (= cc 1) end)) - (let* ((CTXT (matlab-with-context-line lvl1 - (matlab-line-end-of-code lvl1) + (let* ((CTXT (matlab-with-context-line boc-lvl1 + (matlab-line-end-of-code boc-lvl1) (matlab-re-search-keyword-backward (matlab-keyword-regex 'end) (point-at-bol) t) (matlab-scan-block-start-context)))) From 4e35f8bf8516c26c4fa3d919bbe28f2d8f9f7136 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 1 Apr 2021 21:23:00 -0400 Subject: [PATCH 148/168] matlab.el: (matlab-indent-region, matlab-indent-line): Remove use of matlab-navigation-syntax. I don't think we need this anymore - and it messes up optimzations w/ syntax-ppss anyway. (matlab-next-line-indentation): When checking for block-middle or block-case, pass in the correct lvl1 context for analysis. Fixes bug w/ indentaion after a middle or case that has a continuation. tests/continuations.m: Add new test cases for elsif and case with continuation. --- matlab.el | 54 +++++++++++++++++++++---------------------- tests/continuations.m | 24 +++++++++++++++++++ 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/matlab.el b/matlab.el index a8c8fbf..d5b057d 100644 --- a/matlab.el +++ b/matlab.el @@ -1718,37 +1718,35 @@ If there isn't one, then return nil, point otherwise." Unlike `indent-region-line-by-line', this function captures parsing state and re-uses that state along the way." (interactive) - (matlab-navigation-syntax - (save-excursion - (setq end (copy-marker end)) - (goto-char start) - (let ((pr (when (and (not (minibufferp)) (not noprogress)) - (make-progress-reporter "MATLAB Indenting region..." (point) end))) - (lvl2 nil) - (lvl1 nil) - ) - (while (< (point) end) - (unless (and (bolp) (eolp)) - ;; This is where we indent each line - (setq lvl1 (matlab-compute-line-context 1) - lvl2 (matlab-compute-line-context 2 lvl1));; lvl2)) - (when (matlab--indent-line lvl2) - ;; If the indent changed something, refresh this - ;; context obj. - ;;(matlab-refresh-line-context-lvl2 lvl2) - )) - (forward-line 1) - (and pr (progress-reporter-update pr (point)))) - (and pr (progress-reporter-done pr)) - (move-marker end nil))))) + (save-excursion + (setq end (copy-marker end)) + (goto-char start) + (let ((pr (when (and (not (minibufferp)) (not noprogress)) + (make-progress-reporter "MATLAB Indenting region..." (point) end))) + (lvl2 nil) + (lvl1 nil) + ) + (while (< (point) end) + (unless (and (bolp) (eolp)) + ;; This is where we indent each line + (setq lvl1 (matlab-compute-line-context 1) + lvl2 (matlab-compute-line-context 2 lvl1)) ;; lvl2)) + (when (matlab--indent-line lvl2) + ;; If the indent changed something, refresh this + ;; context obj. + ;;(matlab-refresh-line-context-lvl2 lvl2) + )) + (forward-line 1) + (and pr (progress-reporter-update pr (point)))) + (and pr (progress-reporter-done pr)) + (move-marker end nil)))) (defun matlab-indent-line () "Indent a line in `matlab-mode'." (interactive) - (matlab-navigation-syntax - (let ((lvl2 (matlab-compute-line-context 2))) - (matlab--indent-line lvl2)))) + (let ((lvl2 (matlab-compute-line-context 2))) + (matlab--indent-line lvl2))) (defvar matlab--change-indentation-override #'matlab--change-indentation "Tests to override this to validate indent-region.") @@ -2143,8 +2141,8 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (cc (matlab-line-count-closed-blocks lvl1 boc-lvl1)) (bc (matlab-line-count-open-blocks boc-lvl1 lvl1)) (end (matlab-line-end-p boc-lvl1)) - (mc (and (matlab-line-block-middle-p lvl1) 1)) - (ec (and (matlab-line-block-case-p lvl1) 1)) + (mc (and (matlab-line-block-middle-p boc-lvl1) 1)) + (ec (and (matlab-line-block-case-p boc-lvl1) 1)) (ci (current-indentation))) ;; When CC is positive, and END is false, or CC > 1, then the NEXT diff --git a/tests/continuations.m b/tests/continuations.m index a52bd4f..907ec4b 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -92,6 +92,30 @@ function continuations(a,b) %!!0 ... % Continuation by itself just before an end. end %!!0 +function c=expression_cont(a,b) + + if (a > 0 && ... %!!4 + b < 0) %!!8 + + % comment one !!8 + c=1; %!!8 + elseif (a < 0 && ... %!!4 + b > 0) %!!12 + + % comment two !!8 + c=2; %!!8 + end %!!4 + + switch a %!!4 + case 'ert' %!!6 + b = 1; %!!8 + case {'sim', ... %!!6 + 'normalsim'} %!!12 + b=2; %!!8 + end %!!4 + +end + function a=odd_if_location(n) %!!0 i=1; while i<10, if xfcn(i,n)==0, i=i+1; %!!4 From 398667e53f26045ecd9a9e37cb3eb8a792ef96ed Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 2 Apr 2021 20:29:53 -0400 Subject: [PATCH 149/168] matlab-scan.el: (matlab-scan-comment-help-p): After finding prev code line, move to beginning of that command before detecting the declaration. (matlab-scan-beginning-of-command): Add a code-only input to skip comment scanning. tests/continuations.m: Add tests with continuations in function declarations to better test new fcn comment scanning. --- matlab-scan.el | 61 ++++++++++++++++++++++++++----------------- tests/continuations.m | 18 +++++++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index f3d59bb..48174ef 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -785,17 +785,22 @@ Optional PT, if non-nil, means return the point instead of column" (when (matlab-line-comment-p lvl1) ;; Try to get from lvl2 context - (let ((c-lvl1 (when lvl2 (matlab-previous-code-line lvl2)))) - (unless c-lvl1 - ;; If not, compute it ourselves. - (save-excursion - (beginning-of-line) - (forward-comment -100000) - (setq c-lvl1 (matlab-compute-line-context 1)))) - ;; On previous code line - was it a declaration? - (when (matlab-line-declaration-p c-lvl1) - (matlab-with-context-line c-lvl1 - (if pt (point) (current-indentation)))))))) + (let ((c-lvl1 (when lvl2 (matlab-previous-code-line lvl2))) + (boc-lvl1 nil)) + (save-excursion + (unless c-lvl1 + ;; If not, compute it ourselves. + (save-excursion + (beginning-of-line) + (forward-comment -100000) + (setq c-lvl1 (matlab-compute-line-context 1)))) + ;; On previous line, move to beginning of that command. + (matlab-scan-beginning-of-command c-lvl1 'code-only) + (setq boc-lvl1 (matlab-compute-line-context 1)) + ;; On previous code line - was it a declaration? + (when (matlab-line-declaration-p boc-lvl1) + (matlab-with-context-line boc-lvl1 + (if pt (point) (current-indentation))))))))) (defun matlab-scan-previous-line-ellipsis-p () "Return the position of the previous line's continuation if there is one. @@ -815,23 +820,32 @@ This is true iff the previous line has an ellipsis." (when (and csc (= (car (syntax-after csc)) 11)) csc))))) -(defun matlab-scan-beginning-of-command (&optional lvl1) +(defun matlab-scan-beginning-of-command (&optional lvl1 code-only) "Return point in buffer at the beginning of this command. -This function walks up any enclosing parens, and skips -backward over lines that include ellipsis." +This function walks up any preceeding comments, enclosing parens, and skips +backward over lines that include ellipsis. +If optional CODE-ONLY is specified, it doesn't try to scan over +preceeding comments." (unless lvl1 (setq lvl1 (matlab-compute-line-context 1))) ;; If we are in a block comment, just jump to the beginning, and ;; that's it. - (let ((bcs (matlab-line-block-comment-start lvl1))) - (when bcs - (goto-char bcs) - (setq lvl1 (matlab-compute-line-context 1))) + (let ((bcs nil)) + + (goto-char (matlab-line-point lvl1)) + + (when (not code-only) + ;; Code only branch skips comments so the help-comment + ;; routine can also use this function. + (setq bcs (matlab-line-block-comment-start lvl1)) + (when bcs + (goto-char bcs) + (setq lvl1 (matlab-compute-line-context 1))) - ;; If we are in a help comment, jump over that first. - (setq bcs (matlab-scan-comment-help-p lvl1 'point)) - (when bcs - (goto-char bcs) - (setq lvl1 (matlab-compute-line-context 1))) + ;; If we are in a help comment, jump over that first. + (setq bcs (matlab-scan-comment-help-p lvl1 'point)) + (when bcs + (goto-char bcs) + (setq lvl1 (matlab-compute-line-context 1)))) ;; Now scan backward till we find the beginning. (let ((found nil)) @@ -1225,7 +1239,6 @@ Items 2 and 3 are likely the same but could be different." (lvl1-bgn (save-excursion (matlab-scan-beginning-of-command lvl1-match) (matlab-compute-line-context 1)))) (list keyword column lvl1-match lvl1-bgn)))) - ;;; Caching diff --git a/tests/continuations.m b/tests/continuations.m index 907ec4b..bd0f9fa 100644 --- a/tests/continuations.m +++ b/tests/continuations.m @@ -92,7 +92,25 @@ function continuations(a,b) %!!0 ... % Continuation by itself just before an end. end %!!0 +function [ a, ... !!0 + b, ... !!11 + c, ... !!11 + d ] ... !!11 + = continuations_in_return(opt) +% H1 Comment Line !!0 + + code(); %!! 4 + +end %!! 0 + +function [ a, b ] = continuation_in_args(a,... + b) %!!41 +% H1 comment line !!0 + +end + function c=expression_cont(a,b) +% H1 line !!0 if (a > 0 && ... %!!4 b < 0) %!!8 From 9438d95d55840c399584a59698d0a1774401eaf7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 2 Apr 2021 20:31:55 -0400 Subject: [PATCH 150/168] matlab.el: (matlab-fl-opt-continuation, matlab-fl-opt-whitespace) (matlab-fl-fcn-key, matlab-fl-return-args) (matlab-fl-fcn-name, matlab-fl-fcn-args): New regex parts for building font lock keywords. (matlab-function-font-lock-keywords): Revamp using above keyword parts. Now handles continuations between different parts of the function declaration. tests/fontlock.m: New tests for various function declaration flavors with and without ellipsis between differentparts. --- matlab.el | 58 ++++++++++++++++++++++++++++-------------------- tests/fontlock.m | 21 ++++++++++++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/matlab.el b/matlab.el index d5b057d..0215f55 100644 --- a/matlab.el +++ b/matlab.el @@ -1036,38 +1036,48 @@ This matcher will handle a range of variable features." )) "Basic Expressions to highlight in MATLAB Files.") -(defconst matlab-function-arguments - "\\(([^)]*)\\)?\\s-*\\([,;\n%]\\|$\\)") +(defconst matlab-fl-opt-continuation "\\s<\\S>+\\s>") +(defconst matlab-fl-opt-whitespace (concat "\\s-*\\(?:" + matlab-fl-opt-continuation + "\\)?\\s-*")) + +(defconst matlab-fl-fcn-key "^\\s-*function\\_>") +(defconst matlab-fl-return-args "\\(\\[[^]]*\\]\\|\\sw+\\)") +(defconst matlab-fl-fcn-name "\\(?:[sg]et\\.\\)?\\sw+") +(defconst matlab-fl-fcn-args "\\(?:(\\|$\\|\\s<\\)" ) (defconst matlab-function-font-lock-keywords (list ;; defining a function, a (possibly empty) list of assigned variables, ;; function name, and an optional (possibly empty) list of input variables - (list (concat "^\\s-*\\(function\\)\\_>[ \t\n.]*" - "\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*" - "=[ \t\n.]*\\(\\(?:[sg]et\\.\\)?\\sw+\\)[ \t\n.]*" - matlab-function-arguments) - ;;'(1 font-lock-keyword-face append) - handled as keyword - '(2 font-lock-variable-name-face append) - '(3 font-lock-function-name-face prepend)) + (list (concat matlab-fl-fcn-key matlab-fl-opt-whitespace + matlab-fl-return-args matlab-fl-opt-whitespace + "=" matlab-fl-opt-whitespace + "\\(" matlab-fl-fcn-name "\\)" matlab-fl-opt-whitespace + matlab-fl-fcn-args) + '(1 font-lock-variable-name-face append) + '(2 font-lock-function-name-face prepend)) ;; defining a function, a function name, and an optional (possibly ;; empty) list of input variables - (list (concat "^\\s-*\\(function\\)[ \t\n.]+" - "\\(\\(?:[sg]et\\.\\)?\\sw+\\)[ \t\n.]*" - matlab-function-arguments) - ;; '(1 font-lock-keyword-face append) - handled as keyword - '(2 font-lock-function-name-face prepend)) + (list (concat matlab-fl-fcn-key matlab-fl-opt-whitespace + "\\(" matlab-fl-fcn-name "\\)" matlab-fl-opt-whitespace + matlab-fl-fcn-args) + '(1 font-lock-function-name-face prepend)) ;; Anchor on the function keyword, highlight params - (list (concat "^\\s-*function\\>[ \t\n.]*" - "\\(\\(\\[[^]]*\\]\\|\\sw+\\)[ \t\n.]*=[ \t\n.]*\\)?" - "\\(?:[sg]et\\.\\)?\\sw+\\s-*(") - '("\\s-*\\(\\sw+\\)\\s-*[,)]" - (save-excursion - (condition-case nil - (matlab-scan-end-of-command) - (error (point-at-eol)))) - nil - (1 font-lock-variable-name-face))) + (list (concat matlab-fl-fcn-key matlab-fl-opt-whitespace + "\\(" matlab-fl-return-args matlab-fl-opt-whitespace + "=" matlab-fl-opt-whitespace + "\\)?" + matlab-fl-fcn-name matlab-fl-opt-whitespace + "(") + (list (concat matlab-fl-opt-whitespace "\\(\\sw+\\)" + matlab-fl-opt-whitespace "[,)]") + '(save-excursion + (condition-case nil + (matlab-scan-end-of-command) + (error (point-at-eol)))) + nil + '(1 font-lock-variable-name-face))) ;; ARGUMENTS have variables to highlight '(matlab-font-lock-args-keyword-match (matlab-font-lock-anchor-variable-match ;; matcher fcn diff --git a/tests/fontlock.m b/tests/fontlock.m index 626caa9..cb5655d 100644 --- a/tests/fontlock.m +++ b/tests/fontlock.m @@ -141,6 +141,27 @@ function helptest() end +function name_no_args +%^ ^kw ^fn +end + +function retarg = ret_and_name_no_args % comment +%^ ^kw ^vn ^df ^fn ^co +end + +function retarg = args_have_cont (arg_1, ... + arg_2) +%^ ^vn +end + +function [ retarg1, ... + retarg2, ... + retarg3 ] ... + = name_of_fcn (arg1) +%^ ^df ^fn ^vn ^df + +end + classdef (Abstract) myclass < handle %^ ^kw ^ty ^fn ^bi ^cn end From c934a0c0f989d5033fe74a43e4f02294381ec563 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 2 Apr 2021 20:43:01 -0400 Subject: [PATCH 151/168] matlab.el: (matlab-indent-past-arg1-functions): Wrap expression to only be whole-symbols. (matlab-function-font-lock-keywords): Accomodate change to above. tests/indents.el: Add test that used to indent as if it were a fcn that indents past arg1, but shouldn't. --- matlab.el | 4 ++-- tests/indents.m | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/matlab.el b/matlab.el index 0215f55..bbbc690 100644 --- a/matlab.el +++ b/matlab.el @@ -122,7 +122,7 @@ should be ok." :type 'sexp) (defcustom matlab-indent-past-arg1-functions - "[sg]et\\(_param\\)?\\|waitfor\\|notify" + "\\_<\\([sg]et\\(_param\\)?\\|waitfor\\|notify\\)\\_>" "*Regex describing functions whose first arg is special. This specialness means that all following parameters which appear on continued lines should appear indented to line up with the second @@ -1100,7 +1100,7 @@ This matcher will handle a range of variable features." '("\\_<\\(case\\|switch\\)\\_>\\s-+\\({[^}\n]+}\\|[^,%\n]+\\)" (2 font-lock-reference-face)) ;; set_param and waitfor have input variables that can be highlighted. - (list (concat "\\_<" matlab-indent-past-arg1-functions "\\_>\\s-*") + (list (concat matlab-indent-past-arg1-functions "\\s-*") '("(\\s-*\\(\\w+\\)\\s-*\\(,\\|)\\)" nil nil (1 font-lock-variable-name-face))) ) diff --git a/tests/indents.m b/tests/indents.m index 9bb9c51..8b1ca8f 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -194,6 +194,16 @@ function out = array_constant_decls() 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 From 61b24bec9414995f7c16f34ae06ad88a5684120d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 2 Apr 2021 20:55:46 -0400 Subject: [PATCH 152/168] matlab.el: (matlab--previous-line-indent-recommendation): If scanning backwards finds empty line @ beginning of buffer, then just return 0. tests/indents.m: Add blank first line to exercise above. --- matlab.el | 8 +++++++- tests/indents.m | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index bbbc690..72eb837 100644 --- a/matlab.el +++ b/matlab.el @@ -1824,7 +1824,13 @@ line of code. It then scans that line and recommends either: ;; on it's own context, like being a block open or continuation. (let ((prevcmd (matlab-previous-code-line lvl2))) (matlab-with-context-line prevcmd - (matlab-next-line-indentation prevcmd)))))) + (cond + ((and (matlab-line-empty-p prevcmd) + (save-excursion (beginning-of-line) (bobp))) + ;; Beginning of buffer - do no work, just return 0. + 0) + (t + (matlab-next-line-indentation prevcmd)))))))) (defconst matlab-functions-have-end-should-be-true diff --git a/tests/indents.m b/tests/indents.m index 8b1ca8f..95274fe 100644 --- a/tests/indents.m +++ b/tests/indents.m @@ -1,3 +1,4 @@ + function indents(a,b,stuff,cmddual1fake,cmddual2fake) % Help text % !!0 From 84dab231ea264971f618bc261c46e9fad2bca2a2 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 2 Apr 2021 22:28:15 -0400 Subject: [PATCH 153/168] matlab.el: (matlab-indent-function-body, matlab-functions-have-end): Make safe local variable (needed in tests.) (matlab-line-count-open-blocks, matlab-line-count-closed-blocks): DELETE - replaced by ... (matlab-line-count-block-change): New. Return + or - num based on num of opened or closed blocks that don't cancel eachother out. Cuts keyword search on line in half. (matlab-next-line-indentation): Use `matlab-line-count-block-change' replacing old calls. Simplify how functions w/ no indent works. Simplify indent based on indenting block starts and ends. Simplify number of local variables. tests/metest.el: Add new test file for detect and indent. tests/mfuncnofuncindent.m: New file with functions with ends but no indent. Setting w/ local variable. Verify this mode works. --- matlab.el | 164 +++++++++++++------------------------- tests/metest.el | 4 +- tests/mfuncnofuncindent.m | 32 ++++++++ 3 files changed, 90 insertions(+), 110 deletions(-) create mode 100644 tests/mfuncnofuncindent.m diff --git a/matlab.el b/matlab.el index 72eb837..4f62377 100644 --- a/matlab.el +++ b/matlab.el @@ -179,8 +179,8 @@ changed, and functions are indented based on `matlab-functions-have-end'." (const :tag "MathWorks Standard" MathWorks-Standard)) ) - (make-variable-buffer-local 'matlab-indent-function-body) +(put 'matlab-indent-function-body 'safe-local-variable #'symbolp) (defcustom matlab-functions-have-end 'guess "*If non-nil, functions-have-end minor mode is on by default. @@ -190,6 +190,7 @@ If the value is 'guess, then we guess if a file has end when :type 'boolean) (make-variable-buffer-local 'matlab-functions-have-end) +(put 'matlab-functions-have-end 'safe-local-variable #'symbolp) (defun matlab-toggle-functions-have-end () "Toggle `matlab-functions-have-end-minor-mode'." @@ -1635,70 +1636,35 @@ Travells a cross continuations" (forward-line -1) (matlab-line-end-comment-column (matlab-compute-line-context 1)))))) -(defun matlab-line-count-open-blocks (lvl1 end-lvl1) - "Return a the number of unterminated block constructs. -This is any block, such as if or for, that doesn't have an END on this line. -Input LVL1 indicates the line we are searching. -Input END-LVL1 indicates the line whose end we search until." - (matlab-with-context-line lvl1 - (let* ((v 0) - (bound (matlab-line-end-of-code end-lvl1)) - (keyword nil)) - - (if (or (matlab-line-comment-p lvl1) - (matlab-line-empty-p lvl1)) - ;; If this line is comments or empty, no code to scan - 0 - (goto-char (matlab-line-point lvl1)) - (while (setq keyword - (matlab-re-search-keyword-forward (matlab-keyword-regex 'indent) bound t)) - (if (or (and (eq (car keyword) 'mcos) - (not (matlab--valid-mcos-keyword-point nil))) - (and (eq (car keyword) 'args) - (not (matlab--valid-arguments-keyword-point nil)))) - ;; Not valid, skip it. - nil - ;; Increment counter, move to end. - (setq v (1+ v)) - (let ((p (point))) - (forward-word -1) - (if (matlab--scan-block-forward bound) - (goto-char p) - (setq v (1- v)))))) - v)))) +(defun matlab-line-count-block-change (&optional lvl1-start lvl1-end) + "Count the change in block depth across lines. +Start at LVL1-START, and end at LVL1-END. It is ok if these +are the same line. +A positive number means there were more block starts and ends. +A negative number means there were more ends than starts. +0 means no blocks, or that all blocks started also ended." + ;; TODO - delete this + (unless lvl1-start (setq lvl1-start (matlab-compute-line-context 1) + lvl1-end lvl1-start)) -(defun matlab-line-count-closed-blocks (lvl1 bound-lvl1) - "Return the number of closing block constructs on this line. -Argument LVL1 is the line we'll start on (at the end). -Argument BOUND-LVL1 is where to stop search backwards from." - (matlab-with-context-line lvl1 - (let ((v 0) - ;;(lvl1 (matlab-compute-line-context 1)) - (bound (matlab-line-point bound-lvl1)) - ) - - (if (matlab-line-comment-p lvl1) - ;; If this is even vagely a comment line, then there is no - ;; need to do any scanning. - 0 - ;; Else, lets scan. - ;; lets only scan from the beginning of the comment - (matlab-line-end-of-code lvl1) + (if (or (matlab-line-comment-p lvl1-start) + (matlab-line-empty-p lvl1-start)) + ;; If we are starting on comments or empty, no code to scan + 0 + (matlab-with-context-line lvl1-start + (let ((depth 0) + (bounds (matlab-line-end-of-code lvl1-end)) + (keyword nil)) + ;; Lets scan for keywords. + (goto-char (matlab-line-point lvl1-start)) + (while (setq keyword (matlab--scan-next-keyword 'blocks bounds)) + (cond ((eq (car keyword) 'end) + (setq depth (1- depth))) + (t + (setq depth (1+ depth))))) + + depth)))) - ;; Count every END from our starting point till the beginning of the - ;; command. That count indicates unindent from the beginning of the - ;; command which anchors the starting indent. - (goto-char (matlab-line-end-of-code lvl1)) - (while (matlab-re-search-keyword-backward (matlab-keyword-regex 'end) bound t) - (let ((startmove (match-end 0)) - (nomove (point))) - ;; Lets count these end constructs. - (setq v (1+ v)) - (if (matlab--scan-block-backward bound) - (goto-char nomove) - (setq v (1- v))) ;; If whole block, uncount the end - )) - (if (<= v 0) 0 v))))) (defun matlab-function-called-at-point () "Return a string representing the function called nearby point." @@ -2152,19 +2118,20 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (save-excursion (matlab-scan-beginning-of-command lvl1) - ;;(back-to-indentation) (let* ((boc-lvl1 (matlab-compute-line-context 1)) - (cc (matlab-line-count-closed-blocks lvl1 boc-lvl1)) - (bc (matlab-line-count-open-blocks boc-lvl1 lvl1)) + (depthchange (matlab-line-count-block-change boc-lvl1 lvl1)) (end (matlab-line-end-p boc-lvl1)) - (mc (and (matlab-line-block-middle-p boc-lvl1) 1)) - (ec (and (matlab-line-block-case-p boc-lvl1) 1)) - (ci (current-indentation))) - - ;; When CC is positive, and END is false, or CC > 1, then the NEXT - ;; line should have an indent that matches the context at the beginning - ;; of the block of the last end. - (if (or (> cc 1) (and (= cc 1) end)) + ) + + ;; When DEPTHCHANGE is negative, and END is false, or + ;; DEPTHCHANGE < -1, then the NEXT line should have an indent + ;; that matches the context at the beginning of the block of + ;; the last end. + ;; + ;; If we don't do this, and a block-start is not the FIRST item + ;; on a line, then there is no way for the following line to figure + ;; out where it should be. + (if (or (< depthchange -1) (and (= depthchange -1) end)) (let* ((CTXT (matlab-with-context-line boc-lvl1 (matlab-line-end-of-code boc-lvl1) (matlab-re-search-keyword-backward @@ -2172,43 +2139,24 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (matlab-scan-block-start-context)))) (matlab-line-indentation (nth 3 CTXT))) - ;; Old technique. - - ;; When the current point is on a line with a function, the value of bc will - ;; reflect the function in a block count iff if matlab-functions-have-end is - ;; true. However, if matlab-indent-function-body-p is false, there should be - ;; no actual indentation, so bc needs to be decremented by 1. Similarly, if - ;; on a line with an end that closes a function, bc needs to be decremented - ;; by 1 if matlab-functions-have-end is true and matlab-indent-function-body-p - ;; is false. However, just to be safe, indentation is not allowed to go - ;; negative. Thus: - (if matlab-functions-have-end - (if (and - (not (matlab-indent-function-body-p)) - (or (matlab-line-declaration-p lvl1) - (and (matlab-line-end-p lvl1) - (save-excursion - (matlab-backward-sexp t) - (looking-at "function\\b"))))) - (if (> bc 0) - (setq bc (1- bc)) - (if (>= ci matlab-indent-level) - (setq bc -1)))) - ;; Else, funtions don't have ends in this file. - (if (and (matlab-indent-function-body-p) - (matlab-line-declaration-p lvl1)) - (setq bc (1+ bc)))) + ;; Indent recommendations not related to ENDS + + ;; If we are NOT indenting our functions and we are on + ;; a declaration, then we should subtract 1 from the beginning count. + ;; This fairly simple change removes a big chunk of the old code. + (when (and (not (matlab-indent-function-body-p)) + (matlab-line-declaration-p boc-lvl1)) + (setq depthchange (1- depthchange))) + ;; Remove 1 from the close count if there is an END on the beginning ;; of this line, since in that case, the unindent has already happened. - (when end (setq cc (1- cc))) + (when end (setq depthchange (1+ depthchange))) + ;; Calculate the suggested indentation. - (+ ci - (* matlab-indent-level bc) - (* matlab-indent-level (or mc 0)) - (* matlab-indent-level (- cc)) - (* (if (listp matlab-case-indent-level) - (cdr matlab-case-indent-level) matlab-case-indent-level) - (or ec 0)) + (+ (current-indentation) + (* matlab-indent-level depthchange) + (* matlab-indent-level (if (matlab-line-block-middle-p boc-lvl1) 1 0)) + (* (cdr matlab-case-indent-level) (if (matlab-line-block-case-p boc-lvl1) 1 0)) )))))) ;;; The return key ============================================================ diff --git a/tests/metest.el b/tests/metest.el index b5570ad..1cbc959 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -102,7 +102,7 @@ 0) )) -(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" "mfuncspacey.m" "mfuncnoendindent.m" ) +(defvar met-end-detect-files '("empty.m" "stringtest.m" "mfuncnoend.m" "mfuncnoendblock.m" "mfuncends.m" "mclass.m" "mfuncspacey.m" "mfuncnoendindent.m" "mfuncnofuncindent.m") "List of files for running end detection tests on.") (defvar metest-end-detect-test (cons "END detection" met-end-detect-files)) @@ -324,7 +324,7 @@ (list cnt "test"))) -(defvar met-indents-files '("indents.m" "continuations.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m" "mclass_cont.m") +(defvar met-indents-files '("indents.m" "continuations.m" "mclass.m" "blocks.m" "mfuncends.m" "mfuncnoendblock.m" "mclass_cont.m" "mfuncnofuncindent.m") "List of files for running syntactic indentation tests.") (defun metest-indents-randomize-files () diff --git a/tests/mfuncnofuncindent.m b/tests/mfuncnofuncindent.m new file mode 100644 index 0000000..e6f5be2 --- /dev/null +++ b/tests/mfuncnofuncindent.m @@ -0,0 +1,32 @@ +function mfuncnofuncindent( ) +% Test function that has ends for each function. !!0 +% !!0 +% Used with test harness to validate indentation and end detection. !!0 +% !!0 +% %%%function function nil + +fcn_call(); %!!0 + +if condition %!!0 + fcn_call(); %!!4 + fcn_call ... !!4 + (); %!!8 +end %!!0 + +end%!!0 - also no space after end + +function a=fcn_call(inp) %!!0 + +while inp > 0 %!!0 + fcn_call(inp-1); %!!4 + a = [ 1 2 ... !!4 + 3 4 ]; %!!10 +end %!!0 + +end %!!0 + +%{ +% Local Variables: +% matlab-indent-function-body: nil +% End: +%} \ No newline at end of file From 509796c0793528f50e46a2359e1e3c15131c034b Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 2 Apr 2021 22:42:32 -0400 Subject: [PATCH 154/168] matlab.el: (matlab-calculate-indentation): Optimize calling `matlab--previous-line-indent-recommendation' only once in case of checking indentation level less than max indent. --- matlab.el | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/matlab.el b/matlab.el index 4f62377..5c9cffa 100644 --- a/matlab.el +++ b/matlab.el @@ -2073,20 +2073,17 @@ this line." (save-excursion (goto-char found-column) (let ((cc (current-column)) - (mi (assoc ?= matlab-maximum-indents))) - + (mi (assoc ?= matlab-maximum-indents)) + (prev-indent (matlab--previous-line-indent-recommendation lvl2))) + (if (looking-at "\\.\\.\\.\\|$") ;; In this case, the user obviously wants the ;; indentation to be somewhere else. - (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) (cdr (cdr mi)))) + (setq tmp (+ prev-indent (cdr (cdr mi)))) ;; If the indent delta is greater than the max, ;; use the max + current - (if (and mi (> (- cc (matlab--previous-line-indent-recommendation lvl2)) (if (listp (cdr mi)) - (car (cdr mi)) - (cdr mi)))) - (setq tmp (+ (matlab--previous-line-indent-recommendation lvl2) (if (listp (cdr mi)) - (cdr (cdr mi)) - (cdr mi)))) + (if (and mi (> (- cc prev-indent) (if (listp (cdr mi)) (car (cdr mi)) (cdr mi)))) + (setq tmp (+ prev-indent (if (listp (cdr mi)) (cdr (cdr mi)) (cdr mi)))) (setq tmp cc)))))) ;; CONTINUATION with nothing special about it. From 9f7a03e83f95a13a679021f90214bdcf41e5dc1d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 4 Apr 2021 17:30:06 -0400 Subject: [PATCH 155/168] matlab-scan.el: (matlab-line-declaration-name): Fix finding name when there are coninuations around the return arguments. --- matlab-scan.el | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 48174ef..e27b06a 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -491,6 +491,7 @@ Return nil for empty and comment only lines." ) ;; Declarations and Names +(defvar matlab-fl-opt-whitespace) (defun matlab-line-declaration-name (&optional lvl1) "Return string name of a declaration on the line. For functions, this is the name of the function. @@ -511,9 +512,15 @@ If the current line is not a declaration, return nil." ((string= (matlab-line-first-word-text lvl1) "function") (forward-word 1) (skip-syntax-forward " ") - (when (looking-at "\\(\\[[^]]+]\\|\\w+\\)\\s-*=") + (forward-comment 10) + (when (looking-at (concat + "\\(\\[[^]]+]\\|\\w+\\)" + matlab-fl-opt-whitespace + "=")) (goto-char (match-end 0)) - (skip-syntax-forward " ")) + (skip-syntax-forward " ") + (forward-comment 10) + ) (setq type 'function start (point) end (save-excursion @@ -524,9 +531,12 @@ If the current line is not a declaration, return nil." ((string= (matlab-line-first-word-text lvl1) "classdef") (forward-word 1) (skip-syntax-forward " ") + (forward-comment 10) (when (looking-at "([^)]+)") (goto-char (match-end 0)) - (skip-syntax-forward " ")) + (skip-syntax-forward " ") + (forward-comment 10) + ) (setq type 'classdef start (point) end (save-excursion From 4d261a86a2bf2800f8f3cf8c92e69b481d2ea539 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 9 Apr 2021 17:19:37 -0400 Subject: [PATCH 156/168] matlab-scan.el: (matlab--valid-arguments-keyword-point): Argument keywords are ok after an 'end' as well - assuming the 'end' is another argument block - but we don't check that part, just hope for the best. tests/blocks.m: Add example with two argument blocks in a row. --- matlab-scan.el | 9 ++++++++- tests/blocks.m | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/matlab-scan.el b/matlab-scan.el index e27b06a..6472d29 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1015,7 +1015,14 @@ Assume basic keyword checks have already been done." (matlab-scan-beginning-of-command) (and (matlab--valid-keyword-point) (matlab--mk-keyword-node)))))) - (string= (nth 1 parent) "function"))))) + (and parent + (or (string= (nth 1 parent) "function") + ;; If not a function, it might be an and, but that end will need to be + ;; reverse tracked to see if it belongs to valid argument block. + (string= (nth 1 parent) "end") + ;; TODO: be more rigid in this detection. + )) + )))) (defun matlab--scan-derive-block-state (providedstate filter) "Return a block state for current point. diff --git a/tests/blocks.m b/tests/blocks.m index 045cc2c..647805f 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -97,8 +97,32 @@ classdef blocks < handle %>>21 function enumeration(~)%!!8 +%^ ^fn end %<<21 + function y = multiple_arg_blocks(a, b, varargin) %!!8 + + arguments %!!12 +%^ ^kw + a uint32 %!!16 +%^ ^vn ^ty + b uint32 %!!16 +%^ ^vn ^ty + end %!!12 +%^ ^kw + + arguments (Repeating) %!!12 +%^ ^kw ^ty + varargin %!!16 +%^ ^vn + end %!!12 +%^ ^kw + + y = a+b+length(varargin); %!!12 + + end + + %>>22 function usestuff(obj)%!!8 % Try using the methods of this object From 6d28272cfc353464470576f804036dbe8c54ea6d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 9 Apr 2021 17:28:09 -0400 Subject: [PATCH 157/168] matlab.el: (matlab-font-lock-anchor-variable-match): . is ok in arguments variable names. tests/blocks.m: Added sample argument block using variable name as struct with . in it. --- matlab.el | 2 +- tests/blocks.m | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index 5c9cffa..bab9f57 100644 --- a/matlab.el +++ b/matlab.el @@ -976,7 +976,7 @@ color support." This matcher will handle a range of variable features." (when (member (nth 1 matlab-fl-anchor-keyword) '("properties" "events" "arguments")) - (let* ((match (re-search-forward "\\(?:^\\|[,;]\\)\\s-+\\(\\w+\\)\\_>" ml-fl-anchor-limit t)) + (let* ((match (re-search-forward "\\(?:^\\|[,;]\\)\\s-+\\(\\(?:\\w+\\|\\.\\)+\\)\\_>" ml-fl-anchor-limit t)) ;; Save this match so we can do a 2nd anchored search for a data type. (md1 (list (match-beginning 1) (match-end 1))) (tm (looking-at diff --git a/tests/blocks.m b/tests/blocks.m index 647805f..317c538 100644 --- a/tests/blocks.m +++ b/tests/blocks.m @@ -121,6 +121,19 @@ classdef blocks < handle y = a+b+length(varargin); %!!12 end + + function linLog(x,y,scale) + arguments(Repeating) %!!12 + x (1,:) double + y (1,:) double + end %!!12 + arguments %!!12 + scale.Plottype(1,1) string %!!16 +%^ ^vn ^vn ^ty ^ty + end %!!12 + + sprintf('%d %d %s\n', x, y, scale); + end %>>22 From 083a5e9e056b9e63a02554df67f3922f7986f3ae Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Fri, 9 Apr 2021 21:31:53 -0400 Subject: [PATCH 158/168] matlab-syntax.el: (matlab-block-comment-start-re, matlab-block-comment-end-re) (matlab-ltype-block-comment-start, matlab-ltype-block-comment-end) (matlab-block-comment-bounds): DELETED - no longer used. matlab.el: (matlab-mode-map): Re-arrange the map. Delete bindings for return and c-j (use default) Add comments and build sections. (matlab-mode): Update comments. Add electric-indent-functions setting. Remove matlab-comment-indent as a setting (use nil) (matlab-return-function, matlab-return, matlab-plain-ret) (matlab-indent-after-ret, matlab-indent-end-before-ret) (matlab-semicolon-on-return, matlab-indent-before-ret) (matlab-linefeed): DELETED - use default 'newline' and electric indents instead. (matlab-electric-indent-function): NEW - auto-reindent when finishing typing end, else, case, and a few other things. (matlab-electric-comment): Re-write to use new APIs from matlab-scan.el. This enabled deletion of block comment stuff from matlab-syntax.el. (matlab-comment-line-break-function): Remove use of 'matlab-indent-line'. (matlab-comment-indent): Delete (see change in matlab-mode) tests/metest.el: Stop using `matlab-block-comment-bounds'. Replace with new API call. --- matlab-syntax.el | 52 ------------ matlab.el | 204 +++++++++++++++-------------------------------- tests/metest.el | 5 +- 3 files changed, 68 insertions(+), 193 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 4663984..17cf097 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -473,58 +473,6 @@ Returns non-nil if the cursor moved." (goto-char start) (error "Error navitaging syntax.")) t))) - -;;; Block Comment handling -;; -;; Old version block comments were handled in a special way. -;; Can we simplify with syntax tables? -(defconst matlab-block-comment-start-re "^\\s-*%{\\s-*$" - "Regexp that matches the beginning of a block comment. -Block comment indicators must be on a line by themselves.") - -(defconst matlab-block-comment-end-re "^\\s-*%}\\s-*$" - "Regexp that matches the end of a block comment. -Block comment indicators must be on a line by themselves.") - -(defun matlab-ltype-block-comment-start () - "Return non-nil if the current line is a block comment start." - (save-excursion - (beginning-of-line) - (looking-at matlab-block-comment-start-re))) - -(defun matlab-ltype-block-comment-end () - "Return non-nil if the current line is a block comment end." - (save-excursion - (beginning-of-line) - (looking-at matlab-block-comment-end-re))) - -(defun matlab-block-comment-bounds (&optional linebounds) - "Return start and end positions of block comment if we are in one. -Optional LINEBOUNDS specifies if returned limits are line based instead -of character based." - (let* ((pps (syntax-ppss (point))) - (origin (point)) - (start (nth 8 pps)) - (end 0)) - ;; 4 is comment flag. 7 is '2' if block comment - (when (and (nth 4 pps) (eq (nth 7 pps) 2)) - (save-excursion - (goto-char start) - (forward-comment 1) - (setq end (point))) - (when (< end origin) - (goto-char origin) - (error "Error navitaging block comment syntax.")) - (if linebounds - ;; Bounds expanded to beginning/end of the line - (cons (save-excursion - (goto-char start) - (point-at-bol)) - (save-excursion - (goto-char end) - (point-at-eol))) - ;; Just the bounds - (cons start end))))) ;;; Navigating Lists ;; diff --git a/matlab.el b/matlab.el index bab9f57..27f8f84 100644 --- a/matlab.el +++ b/matlab.el @@ -504,17 +504,34 @@ point, but it will be restored for them." ;; mode map (defvar matlab-mode-map (let ((km (make-sparse-keymap))) - (define-key km [return] 'matlab-return) - (define-key km "%" 'matlab-electric-comment) - (define-key km "}" 'matlab-electric-block-comment) - (define-key km "{" 'matlab-electric-block-comment) - (define-key km "\C-c;" 'matlab-comment-region) - (define-key km "\C-c:" 'matlab-uncomment-region) - (define-key km [(control c) return] 'matlab-comment-return) + ;; Navigation Commands + (define-key km [(meta a)] 'matlab-beginning-of-command) + (define-key km [(meta e)] 'matlab-end-of-command) + (define-key km [(meta control f)] 'matlab-forward-sexp) + (define-key km [(meta control b)] 'matlab-backward-sexp) + (define-key km [(meta control q)] 'matlab-indent-sexp) + (define-key km [(meta control a)] 'matlab-beginning-of-defun) + (define-key km [(meta control e)] 'matlab-end-of-defun) + ;; Insert, Fill stuff (define-key km [(control c) (control c)] 'matlab-insert-map-fcn) (define-key km [(control c) (control f)] 'matlab-fill-comment-line) (define-key km [(control c) (control j)] 'matlab-justify-line) (define-key km [(control c) (control q)] 'matlab-fill-region) + ;; Comment Stuff + (define-key km "%" 'matlab-electric-comment) + (define-key km "^" 'matlab-electric-comment) + (define-key km "}" 'matlab-electric-block-comment) + (define-key km "{" 'matlab-electric-block-comment) + (define-key km "\C-c;" 'matlab-comment-region) + (define-key km "\C-c:" 'matlab-uncomment-region) + (define-key km [(meta \;)] 'matlab-comment) + (define-key km [(meta j)] 'matlab-comment-line-break-function) + (define-key km [(control c) return] 'matlab-comment-return) + (substitute-key-definition 'comment-region 'matlab-comment-region + km global-map) ;torkel + ;; Completion + (define-key km "\M-\t" 'matlab-complete-symbol) + ;; Connecting to MATLAB Shell (define-key km [(control c) (control s)] 'matlab-shell-save-and-go) (define-key km [(control c) (control r)] 'matlab-shell-run-region) (define-key km [(meta control return)] 'matlab-shell-run-cell) @@ -522,28 +539,12 @@ point, but it will be restored for them." (define-key km [(control c) (control t)] 'matlab-show-line-info) (define-key km [(control c) ?. ] 'matlab-shell-locate-fcn) (define-key km [(control h) (control m)] matlab-help-map) - (define-key km [(control j)] 'matlab-linefeed) - (define-key km "\M-\r" 'newline) - (define-key km [(meta \;)] 'matlab-comment) - (define-key km [(meta a)] 'matlab-beginning-of-command) - (define-key km [(meta e)] 'matlab-end-of-command) - (define-key km [(meta j)] 'matlab-comment-line-break-function) (define-key km [(meta s)] 'matlab-show-matlab-shell-buffer) - (define-key km "\M-\t" 'matlab-complete-symbol) - (define-key km [(meta control f)] 'matlab-forward-sexp) - (define-key km [(meta control b)] 'matlab-backward-sexp) - (define-key km [(meta control q)] 'matlab-indent-sexp) - (define-key km [(meta control a)] 'matlab-beginning-of-defun) - (define-key km [(meta control e)] 'matlab-end-of-defun) - (if (string-match "XEmacs" emacs-version) - (define-key km [(control meta button1)] 'matlab-find-file-click) - (define-key km [(control meta mouse-2)] 'matlab-find-file-click)) - + (define-key km [(control meta mouse-2)] 'matlab-find-file-click) + ;; Debugger interconnect (substitute-key-definition 'read-only-mode 'matlab-toggle-read-only km global-map) - (substitute-key-definition 'comment-region 'matlab-comment-region - km global-map) ;torkel km) "The keymap used in `matlab-mode'.") @@ -1306,7 +1307,6 @@ Variables: `fill-column' Column used in auto-fill. `matlab-indent-function-body' If non-nil, indents body of MATLAB functions. `matlab-functions-have-end' If non-nil, MATLAB functions terminate with end. - `matlab-return-function' Customize RET handling with this function. `matlab-fill-code' Non-nil, auto-fill code in auto-fill-mode. `matlab-fill-strings' Non-nil, auto-fill strings in auto-fill-mode. `matlab-verify-on-save-flag' Non-nil, enable code checks on save. @@ -1340,8 +1340,11 @@ All Key Bindings: (make-local-variable 'comment-column) (setq comment-column matlab-comment-column) (make-local-variable 'comment-indent-function) - (setq comment-indent-function 'matlab-comment-indent) + (setq comment-indent-function (lambda () nil)) ;; always use indent-according-to-mode + (make-local-variable 'electric-indent-functions) + (setq electric-indent-functions 'matlab-electric-indent-function) + (make-local-variable 'add-log-current-defun-function) (setq add-log-current-defun-function 'matlab-current-defun) ;; Emacs 20 supports this variable. @@ -1372,7 +1375,7 @@ All Key Bindings: matlab-file-gaudy-font-lock-keywords matlab-file-really-gaudy-font-lock-keywords ) - nil ; matlab-syntax supports comments and strings. + nil ; use syntax table comments/strings nil ; keywords are case sensitive. ;; This puts _ as a word constituent, ;; simplifying our keywords significantly @@ -1380,6 +1383,7 @@ All Key Bindings: (setq font-lock-multiline 'undecided) (add-to-list 'font-lock-extend-region-functions #'matlab-font-lock-extend-region t) + ;; Highilght parens OR if/end type blocks (make-local-variable 'show-paren-data-function) (setq show-paren-data-function 'matlab-show-paren-or-block) @@ -1499,7 +1503,7 @@ This assumes that expressions do not cross \"function\" at the left margin." ;; No auto-end .... ;; End of a block comment - ((matlab-ltype-block-comment-end) + ((eq (matlab-line-comment-style (matlab-compute-line-context 1)) 'block-end) (beginning-of-line) (matlab-beginning-of-string-or-comment)) @@ -2155,73 +2159,21 @@ See `matlab-calculate-indentation' for how the output of this fcn is used." (* matlab-indent-level (if (matlab-line-block-middle-p boc-lvl1) 1 0)) (* (cdr matlab-case-indent-level) (if (matlab-line-block-case-p boc-lvl1) 1 0)) )))))) - -;;; The return key ============================================================ -(defcustom matlab-return-function 'matlab-indent-end-before-ret - "Function to handle return key. -Must be one of: - 'matlab-plain-ret - 'matlab-indent-after-ret - 'matlab-indent-end-before-ret - 'matlab-indent-before-ret" - :group 'matlab - :type '(choice (function-item matlab-plain-ret) - (function-item matlab-indent-after-ret) - (function-item matlab-indent-end-before-ret) - (function-item matlab-indent-before-ret))) - -(defun matlab-return () - "Handle carriage return in `matlab-mode'." - (interactive) - (matlab-semicolon-on-return) - (funcall matlab-return-function)) - -(defun matlab-plain-ret () - "Vanilla new line." - (interactive) - (newline)) - -(defun matlab-indent-after-ret () - "Indent after new line." - (interactive) - (newline) - (matlab-indent-line)) - -(defun matlab-indent-end-before-ret () - "Indent line if block end, start new line, and indent again." - (interactive) - (let ((lvl1 (matlab-compute-line-context 1))) - (when (or (matlab-line-end-p lvl1) - (matlab-line-block-case-p lvl1) - (matlab-line-block-middle-p lvl1) - (matlab-line-declaration-p lvl1)) - (matlab-indent-line))) - (newline) - (matlab-indent-line)) - -(defvar matlab-quiesce-nosemi-regexp) ;; quiet compiler warning (var is defined below) -(defun matlab-semicolon-on-return () - "If needed, add a semicolon at point automatically." - (if matlab-return-add-semicolon - (when (matlab-line-end-of-code-needs-semicolon-p) - (insert ";") - (end-of-line)))) - -(defun matlab-indent-before-ret () - "Indent line, start new line, and indent again." - (interactive) - (matlab-indent-line) - (newline) - (matlab-indent-line)) - -(defun matlab-linefeed () - "Handle line feed in `matlab-mode'. -Has effect of `matlab-return' with (not matlab-indent-before-return)." - (interactive) - (matlab-indent-line) - (newline) - (matlab-indent-line)) +(defun matlab-electric-indent-function (char) + "Return t if `electric-indent-mode' should indent after CHAR is inserted. +Return nil otherwise. +This function recommends indenting after special keywords that typically cause indentation +changes so the code fixes itself up." + (cond ((eq char ?e) + (let ((lvl1 (matlab-compute-line-context 1))) + (or (matlab-line-block-middle-p lvl1) + (matlab-line-block-case-p lvl1)))) + ((eq char ?d) + (let ((lvl1 (matlab-compute-line-context 1))) + (matlab-line-end-p lvl1))) + (t + nil))) ;;; Comment management======================================================== @@ -2237,53 +2189,29 @@ Has effect of `matlab-return' with (not matlab-indent-before-return)." Argument ARG specifies how many %s to insert." (interactive "P") (self-insert-command (or arg 1)) - (matlab-indent-line) - (skip-chars-forward "%")) + (when (or (eq last-command-event ?%) + (and (eq last-command-event ?^) ;; ignore comments move quick back to col0 + (matlab-line-comment-p (matlab-compute-line-context 1)) + (eq (char-before (1- (point))) ?%) + )) + (matlab-indent-line) + (skip-chars-forward "%^"))) (defun matlab-electric-block-comment (arg) "Indent line and insert block comment end character. Argument ARG specifies how many %s to insert." (interactive "P") (self-insert-command (or arg 1)) - (let ((bc (save-excursion (beginning-of-line) (matlab-block-comment-bounds)))) + (let* ((lvl1 (matlab-compute-line-context 1)) + (bc (matlab-line-block-comment-start lvl1))) - (cond ((matlab-ltype-block-comment-start) - - ;; Starting block comment. Check if we are already in a block - ;; comment, and blink it if a problem. - (let ((bcwrapped (save-excursion - (beginning-of-line) - (matlab-block-comment-bounds)))) - - ;; Regardless, indent our line + (cond ((eq (matlab-line-comment-style lvl1) 'block-start) (matlab-indent-line) - - (when bcwrapped - (save-excursion - (goto-char (car bcwrapped)) - (skip-chars-forward "%{") - (message "Nested block comment start %%{") - (pulse-momentary-highlight-region (car bcwrapped) (point)))) - )) - - ;;ELSE, maybe end of block comment - ((and bc (matlab-ltype-block-comment-end)) - (progn - (matlab-indent-line) - ;; The above sometimes puts the cursor on the %, not after it. - (skip-chars-forward "%}") - (pulse-momentary-highlight-region (car bc) (cdr bc))) ) - - ;; Else, not in a comment - which means we don't have - ((and (not bc) (save-excursion - (skip-chars-backward "%{") - (looking-at "%{"))) - (message "Block comment end has no matching %%{") - (save-excursion - (beginning-of-line) - (when (re-search-backward matlab-block-comment-end-re nil t) - (pulse-momentary-highlight-region (match-beginning 0) (match-end 0)))) + ;;ELSE, maybe end of block comment + ((eq (matlab-line-comment-style lvl1) 'block-end) + (matlab-indent-line) + (when bc (pulse-momentary-highlight-region bc (point))) ) ))) @@ -2331,16 +2259,14 @@ Argument ARG specifies how many %s to insert." Optional argument SOFT indicates that the newline is soft, and not hard." (interactive) (if (not (matlab-cursor-in-comment)) - (matlab-return) + (progn (newline);;(matlab-return) + (matlab-indent-line)) ;; Will the below fn work in old emacsen? (if soft (insert-and-inherit ?\n) (newline 1)) (insert "% ") (matlab-indent-line) - (end-of-line))) - -(defun matlab-comment-indent () - "Indent a comment line in `matlab-mode'." - (matlab--calc-indent)) + (back-to-indentation) + (skip-chars-forward "% "))) (defun matlab-comment-region (beg-region end-region arg) "Comments every line in the region. diff --git a/tests/metest.el b/tests/metest.el index 1cbc959..d60a9ca 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -176,13 +176,14 @@ (pt (match-end 1)) (mc (match-string-no-properties 1)) (fnt (get-text-property pt 'face)) - (bc (metest-condition-case-error-msg (matlab-block-comment-bounds))) + (lv1 (matlab-compute-line-context 1)) + (bc (metest-condition-case-error-msg (matlab-line-block-comment-start lv1))) (qd (metest-condition-case-error-msg (matlab-cursor-comment-string-context))) ) (goto-char pt) ;; Test 1 - what are we? - (unless (or (and (string= "b" mc) bc) + (unless (or (and (string= "b" mc) (and bc (eq 'comment qd))) (and (string= "v" mc) (eq 'charvector qd)) (and (string= "V" mc) (eq 'charvector qd)) (and (string= "s" mc) (eq 'string qd)) From 6c833f4426eba2583c9add646d5786e9e5b86ee8 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 10 Apr 2021 07:54:15 -0400 Subject: [PATCH 159/168] matlab-syntax.el: (matlab-move-simple-sexp-backward-internal,matlab-move-simple-sexp-internal): Set `forward-sexp-function' to nil so the next call doesn't route to `matlab-forward-sexp' by accident. matlab.el: Disable custom stuff related to navigating sexp, and instead depend on `forward-sexp-function' to enable built-in Emacs behavior in all cases, not just the special ones we added. (matlab-mode-map): Remove all bindings that do navigation. (matlab-mode): Remove comments about custom nav commands. Set `forward-sexp-function' to `matlab-forward-sexp-function'. (matlab-forward-sexp-fcn): NEW (matlab-backward-sexp, matlab-forward-sexp): Remove interactive key. --- matlab-syntax.el | 6 ++++-- matlab.el | 42 +++++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 17cf097..1c60ad6 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -553,12 +553,14 @@ This could mean it is: ;; Left over old APIs. Delete these someday. (defsubst matlab-move-simple-sexp-backward-internal (count) "Move backward COUNT number of MATLAB sexps." - (forward-sexp (- count))) + (let ((forward-sexp-function nil)) + (forward-sexp (- count)))) (defsubst matlab-move-simple-sexp-internal(count) "Move over one MATLAB sexp COUNT times. If COUNT is negative, travel backward." - (forward-sexp count)) + (let ((forward-sexp-function nil)) + (forward-sexp count))) (provide 'matlab-syntax) diff --git a/matlab.el b/matlab.el index 27f8f84..d990afa 100644 --- a/matlab.el +++ b/matlab.el @@ -507,11 +507,6 @@ point, but it will be restored for them." ;; Navigation Commands (define-key km [(meta a)] 'matlab-beginning-of-command) (define-key km [(meta e)] 'matlab-end-of-command) - (define-key km [(meta control f)] 'matlab-forward-sexp) - (define-key km [(meta control b)] 'matlab-backward-sexp) - (define-key km [(meta control q)] 'matlab-indent-sexp) - (define-key km [(meta control a)] 'matlab-beginning-of-defun) - (define-key km [(meta control e)] 'matlab-end-of-defun) ;; Insert, Fill stuff (define-key km [(control c) (control c)] 'matlab-insert-map-fcn) (define-key km [(control c) (control f)] 'matlab-fill-comment-line) @@ -1279,10 +1274,6 @@ based on the local syntax. Convenient navigation commands are: \\[matlab-beginning-of-command] - Move to the beginning of a command. \\[matlab-end-of-command] - Move to the end of a command. - \\[matlab-beginning-of-defun] - Move to the beginning of a function. - \\[matlab-end-of-defun] - Move do the end of a function. - \\[matlab-forward-sexp] - Move forward over a syntactic block of code. - \\[matlab-backward-sexp] - Move backwards over a syntactic block of code. Convenient template insertion commands: \\[tempo-template-matlab-function] - Insert a function definition. @@ -1341,15 +1332,16 @@ All Key Bindings: (setq comment-column matlab-comment-column) (make-local-variable 'comment-indent-function) (setq comment-indent-function (lambda () nil)) ;; always use indent-according-to-mode - (make-local-variable 'electric-indent-functions) (setq electric-indent-functions 'matlab-electric-indent-function) - + + ;; Sexp's and Defuns + (make-local-variable 'forward-sexp-function) + (setq forward-sexp-function 'matlab-forward-sexp-fcn) (make-local-variable 'add-log-current-defun-function) (setq add-log-current-defun-function 'matlab-current-defun) - ;; Emacs 20 supports this variable. - ;; This lets users turn auto-fill on and off and still get the right - ;; fill function. + + ;; Auto-Fill and Friends (make-local-variable 'normal-auto-fill-function) (setq normal-auto-fill-function 'matlab-auto-fill) (make-local-variable 'fill-column) @@ -1485,7 +1477,6 @@ a block is not terminated.") If optional AUTOEND, then pretend we are at an end. If optional NOERROR, then we return t on success, and nil on failure. This assumes that expressions do not cross \"function\" at the left margin." - (interactive "P") (let ((p (point)) (returnme t) keyword) @@ -1534,13 +1525,27 @@ This assumes that expressions do not cross \"function\" at the left margin." (goto-char p) returnme)) +(defun matlab-forward-sexp-fcn (&optional arg) + "Function used as `forward-sexp-function' for MATLAB mode. +Adapt to use `matlab-forward-sexp' or `matlab-backward-sexp' +depending on value of 'arg'." + ;; Move forward on positive arg. + (while (> arg 0) + (matlab-forward-sexp) + (setq arg (1- arg))) + ;; Or maybe move backward on negative args. + (while (< arg 0) + (matlab-backward-sexp) + (setq arg (1+ arg))) + ) + + (defun matlab-forward-sexp (&optional autostart parentblock) "Go forward one balanced set of MATLAB expressions. If AUTOSTART is non-nil, assume we are already inside a block, and navigate forward until we exit that block. PARENTBLOCK is used when recursing to validate block starts as being in a valid context." - (interactive "P") (let (p keyword) ;; go to here if no error. (save-excursion ;; Don't move if there is an error ;; skip over preceding whitespace @@ -1580,11 +1585,6 @@ a valid context." (setq p (point))) (goto-char p))) -(defun matlab-indent-sexp () - "Indent the syntactic block starting at point." - (interactive) - (indent-region (point) (save-excursion (matlab-forward-sexp) (point)) nil)) - (defun matlab-beginning-of-defun () "Go to the beginning of the current function." (interactive) From bda0b63da30fef5a1d9debb3ac31a3637ee78faa Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 10 Apr 2021 13:54:58 -0400 Subject: [PATCH 160/168] matlab-syntax.el: (matlab-beginning-of-string-or-comment): If 'all-comments' is passed in, always skip those comments even if not in a comment right now. (matlab-end-of-string-or-comment): If 'all-comments' is passed in, always skip those comments even if not in a comment right now. Make a defun, not defsubst since it is getting pretty big. matlab.el: (matlab-mode-menu): Remove navigate menu. No special navigation anymore. (matlab-mode): Setup beginning-of-defun-function and end-of-defun-function. (matlab-beginning-of-defun, matlab-end-of-defun): Update to have input arg like built-in version. Be more robust about skipping over comments between defuns. (matlab-beginning-of-defun-raw): New - from above. (matlab-skip-over-defun): New. (matlab-current-defun): Use raw version. (matlab-show-line-info): Fix bug. Also print name of current defun. --- matlab-syntax.el | 43 +++++++++++-------- matlab.el | 106 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 104 insertions(+), 45 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index 1c60ad6..f285969 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -448,31 +448,38 @@ bounds of the string or comment the cursor is in" "If the cursor is in a string or comment, move to the beginning. Returns non-nil if the cursor moved." (let* ((pps (syntax-ppss (point)))) - (when (nth 8 pps) - (goto-char (nth 8 pps)) - (when all-comments (forward-comment -100000)) - t))) + (prog1 + (when (nth 8 pps) + (goto-char (nth 8 pps)) + t) + (when all-comments (forward-comment -100000))))) -(defsubst matlab-end-of-string-or-comment (&optional all-comments) +(defun matlab-end-of-string-or-comment (&optional all-comments) "If the cursor is in a string or comment, move to the end. If optional ALL-COMMENTS is non-nil, then also move over all adjacent comments. Returns non-nil if the cursor moved." (let* ((pps (syntax-ppss (point))) (start (point))) - (when (nth 8 pps) - ;; syntax-ppss doesn't have the end, so go to the front - ;; and then skip forward. - (goto-char (nth 8 pps)) - (if (nth 3 pps) - (goto-char (scan-sexps (point) 1)) - (forward-comment (if all-comments 100000 1))) - ;; If the buffer is malformed, we might end up before starting pt. - ;; so error. - (when (< (point) start) - (goto-char start) - (error "Error navitaging syntax.")) - t))) + (if (nth 8 pps) + (progn + ;; syntax-ppss doesn't have the end, so go to the front + ;; and then skip forward. + (goto-char (nth 8 pps)) + (if (nth 3 pps) + (goto-char (scan-sexps (point) 1)) + (forward-comment (if all-comments 100000 1))) + ;; If the buffer is malformed, we might end up before starting pt. + ;; so error. + (when (< (point) start) + (goto-char start) + (error "Error navitaging syntax.")) + t) + ;; else not in comment, but still skip 'all-comments' if requested. + (not (eq (point) + (progn (when all-comments (forward-comment 100000)) + (point)))) + ))) ;;; Navigating Lists ;; diff --git a/matlab.el b/matlab.el index d990afa..f5f7e34 100644 --- a/matlab.el +++ b/matlab.el @@ -577,13 +577,6 @@ point, but it will be restored for them." ["Spell check strings and comments" matlab-ispell-strings-and-comments t] ["Quiesce source" matlab-mode-vf-quiesce-buffer t] ) - ("Navigate" - ["Beginning of Command" matlab-beginning-of-command t] - ["End of Command" matlab-end-of-command t] - ["Forward Block" matlab-forward-sexp t] - ["Backward Block" matlab-backward-sexp t] - ["Beginning of Function" matlab-beginning-of-defun t] - ["End of Function" matlab-end-of-defun t]) ("Format" ["Justify Line" matlab-justify-line t] ["Fill Region" matlab-fill-region t] @@ -1338,6 +1331,10 @@ All Key Bindings: ;; Sexp's and Defuns (make-local-variable 'forward-sexp-function) (setq forward-sexp-function 'matlab-forward-sexp-fcn) + (make-local-variable 'beginning-of-defun-function) + (setq beginning-of-defun-function 'matlab-beginning-of-defun) + (make-local-variable 'end-of-defun-function) + (setq end-of-defun-function 'matlab-skip-over-defun) (make-local-variable 'add-log-current-defun-function) (setq add-log-current-defun-function 'matlab-current-defun) @@ -1585,33 +1582,83 @@ a valid context." (setq p (point))) (goto-char p))) -(defun matlab-beginning-of-defun () - "Go to the beginning of the current function." - (interactive) - (if (eq (matlab-on-keyword-p) 'end) - (progn - ;; If we are ON an end an it matches a decl, - ;; that is probably what was indented. - (matlab--scan-block-backward) - (if (eq (matlab-on-keyword-p) 'decl) - nil ; done - (matlab--scan-block-backward-up-until 'decl))) - ;; else, just do a scan. - (matlab--scan-block-backward-up-until 'decl))) +(defun matlab-beginning-of-defun (&optional arg) + "Go to the beginning of the current function. +With optional ARG, go backward that many defuns." + (interactive "p") + (unless arg (setq arg 1)) + (let ((ans nil)) + ;; If ARG is positive, move BACKWARD that many defuns. + (while (> arg 0) + (setq ans (matlab--beginning-of-defun-raw)) + (setq arg (1- arg))) + ;; If ARG is negative, move FORWARD that many defun + (while (< arg 0) + (if (eq (matlab-on-keyword-p) 'decl) + (setq ans (not (matlab--scan-block-forward))) + ;; Else, just look for stuff and hope for the best. + (setq ans (matlab--scan-next-keyword 'decl (point-max))) + ) + (setq arg (1+ arg))) + + ans)) -(defun matlab-end-of-defun () +(defun matlab-skip-over-defun () + "Assigned to `end-of-defun-function' for matlab mode. +Assume point is on a defun, and if so, skip to the end." + (skip-syntax-forward " >") + (if (eq (matlab-on-keyword-p) 'decl) + (matlab--scan-block-forward) + ;; Else, bad condition. Maybe we're moving up from + ;; inside a nested function? If so, bounce up + ;; and try again. + (matlab-end-of-defun 1))) + + +(defun matlab-end-of-defun (&optional arg) "Go to the end of the current function." - (interactive) - (when (not (eq (matlab-on-keyword-p) 'decl)) - (matlab--scan-block-backward-up-until 'decl)) - (matlab--scan-block-forward)) + (interactive "p") + (unless arg (setq arg 1)) + (let ((ans nil)) + (while (> arg 0) + (matlab-end-of-string-or-comment t) + (skip-syntax-forward " ") + (when (not (eq (matlab-on-keyword-p) 'decl)) + (matlab--scan-block-backward-up-until 'decl)) + (skip-syntax-forward " ") + (setq ans + (if (eq (matlab-on-keyword-p) 'decl) + (not (matlab--scan-block-forward)) + nil)) + (setq arg (1- arg))) + ans)) + +(defun matlab--beginning-of-defun-raw () + "Move to the beginning of defun cursor is in. +Move up and backwards one defun, our out of current defun. +Accounts for nested functions." + ;; Get out of comments. + (matlab-beginning-of-string-or-comment t) + ;; back over whitespace - try to find what we are near. + (skip-syntax-backward " >") + ;; Do scanning + (if (not (eq (matlab-on-keyword-p) 'end)) + ;; No end, scan up until we find the declaration we're in. + (matlab--scan-block-backward-up-until 'decl) + ;; Else, nav backward over the end we are at. + (matlab--scan-block-backward) + (if (eq (matlab-on-keyword-p) 'decl) + t ; done + ;; If that end wasn't a decl, scan upward. + (matlab--scan-block-backward-up-until 'decl)))) (defun matlab-current-defun () "Return the name of the current function." (save-excursion - (matlab-beginning-of-defun) + (matlab--beginning-of-defun-raw) (nth 1 (matlab-line-declaration-name)))) + (defun matlab-beginning-of-command () "Go to the beginning of an M command. Travels across continuations." @@ -3142,7 +3189,9 @@ desired. Optional argument FAST is not used." (lvl1msg (matlab-describe-line-indent-context lvl1 t)) (indent nil) (fullindent (matlab--calc-indent lvl2 'indent)) - (nexti (matlab-next-line-indentation nil))) + (nexti (matlab-next-line-indentation lvl1)) + (defn (matlab-current-defun)) + ) (setq msg (concat msg "Line Syntax: " lvl1msg " | Preferred Indents: This: " (int-to-string (nth 1 indent)) @@ -3154,6 +3203,9 @@ desired. Optional argument FAST is not used." (setq msg (concat msg " w/cont"))) (when (matlab-line-end-comment-point lvl1) (setq msg (concat msg " w/comm"))) + (when defn + (setq msg (concat msg " Defun: " defn))) + (message "%s" msg)))) From 4611551deb37a47d168dda957968c67ea4fcaad7 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 11 Apr 2021 15:35:26 -0400 Subject: [PATCH 161/168] matlab-scan.el: (matlab--valid-arguments-keyword-point): If previous code is 'end', make sure it matches a valid arguments block. (matlab-re-search-keyword-forward,matlab-re-search-keyword-backward): When checking if in string/comment, don't pass in 'all comments' as t. Do that only if in a comment. matlab-syntax.el: (matlab-beginning-of-string-or-comment): Make doc accurate. (matlab-end-of-string-or-comment): Fix logic for case when not in a comment to only skip all comments if looking at a comment. Avoid moving pt if just looking at whitespace. matlab.el: (matlab-do-functions-have-end-p): Improve scanning for end to not do condition-case. This was hiding an error that was causing incorrect answer. --- matlab-scan.el | 17 +++++++++++++---- matlab-syntax.el | 9 +++++---- matlab.el | 16 ++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/matlab-scan.el b/matlab-scan.el index 6472d29..51b02dc 100644 --- a/matlab-scan.el +++ b/matlab-scan.el @@ -1019,8 +1019,13 @@ Assume basic keyword checks have already been done." (or (string= (nth 1 parent) "function") ;; If not a function, it might be an and, but that end will need to be ;; reverse tracked to see if it belongs to valid argument block. - (string= (nth 1 parent) "end") - ;; TODO: be more rigid in this detection. + (and (string= (nth 1 parent) "end") + (save-excursion + (goto-char (nth 2 parent)) + (matlab--scan-block-backward) + (let ((prevblock (matlab--mk-keyword-node))) + (string= (nth 1 prevblock) "arguments"))) + ) )) )))) @@ -1200,7 +1205,10 @@ then skip and keep searching." ;; Check for simple cases that are invalid for keywords ;; for strings, comments, and lists, skip to the end of them ;; to not waste time searching for keywords inside. - (cond ((matlab-end-of-string-or-comment t) + (cond ((matlab-end-of-string-or-comment) + ;; Test is only IF in a comment. Also skip other comments + ;; once we know we were in a comment. + (matlab-end-of-string-or-comment t) (setq ans nil)) ((matlab-in-list-p) (condition-case nil @@ -1228,7 +1236,8 @@ then skip and keep searching." ;; Check for simple cases that are invalid for keywords ;; for strings, comments, and lists, skip to the end of them ;; to not waste time searching for keywords inside. - (cond ((matlab-beginning-of-string-or-comment t) + (cond ((matlab-beginning-of-string-or-comment) + (matlab-beginning-of-string-or-comment t) (setq ans nil)) ((matlab-beginning-of-outer-list) (setq ans nil)) diff --git a/matlab-syntax.el b/matlab-syntax.el index f285969..f22b6d8 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -446,11 +446,12 @@ bounds of the string or comment the cursor is in" (defsubst matlab-beginning-of-string-or-comment (&optional all-comments) "If the cursor is in a string or comment, move to the beginning. -Returns non-nil if the cursor moved." +Returns non-nil if the cursor is in a comment." (let* ((pps (syntax-ppss (point)))) (prog1 (when (nth 8 pps) (goto-char (nth 8 pps)) + t) (when all-comments (forward-comment -100000))))) @@ -476,9 +477,9 @@ Returns non-nil if the cursor moved." (error "Error navitaging syntax.")) t) ;; else not in comment, but still skip 'all-comments' if requested. - (not (eq (point) - (progn (when all-comments (forward-comment 100000)) - (point)))) + (when (and all-comments (looking-at "\\s-*\\s<")) + (forward-comment 100000) + t) ))) ;;; Navigating Lists diff --git a/matlab.el b/matlab.el index f5f7e34..7c88cef 100644 --- a/matlab.el +++ b/matlab.el @@ -287,12 +287,16 @@ If the value is t, then return that." (goto-char (point-min)) (matlab-find-code-line) (let ((matlab-functions-have-end t)) ;; pretend we have ends - (beginning-of-line) - (condition-case nil - ;; Try to navigate. If success, then t - (progn (matlab-forward-sexp) t) - ;; On failure, then no ends. - (error nil)) + (back-to-indentation) + (if (eq (matlab-on-keyword-p) 'decl) + ;; If block scaning returns state, then that means + ;; there is a missing end, so value is nil. + ;; If it returns empty, then there is a matching end. + (if (matlab--scan-block-forward) + nil + t) + ;; Not on a decl, therefore just say nil, since block scanning would fail. + nil) )))) ) ;; Else, just return the default. From 94e3625d5bf26ce0a74e860b28d8a476b98cb775 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 15 Apr 2021 13:24:51 -0400 Subject: [PATCH 162/168] matlab.el: (matlab-hg-primitives-list): Fix typo of a command name. (matlab-comment-on-line): Fix this fcn to do what the doc says. mlint.el: (mlint-mark-ok): Separate the 2 cases of existing comment or not and do each with special code. Make sure the OK is put in the right place. --- matlab.el | 10 ++++++---- mlint.el | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/matlab.el b/matlab.el index 7c88cef..ae49e25 100644 --- a/matlab.el +++ b/matlab.el @@ -836,7 +836,7 @@ Argument LIMIT is the maximum distance to search." "plot" "plot3" "semilogx" "semilogy" "loglog" "scatter" "scatter3" "stackedplot" "area" "errorbar" "bubblechart" "bubblechart3" "swarmchart" "swarmchart3" "spy" "histogram" "histogram2" "wordcloud" "bubblecloud" "heatmap" "parallelplot" - "bar" "barh" "bar3" "bar3h" "stem" "stair" "quiver" "quiver3" "stem3" + "bar" "barh" "bar3" "bar3h" "stem" "stairs" "quiver" "quiver3" "stem3" "contour" "contourf" "contour3" "contourslice" "fcontour" ;; 3D "surf" "surfc" "surfl" "ribbon" "pcolor" "mesh" "meshc" "meshz" "waterfall" @@ -1737,9 +1737,11 @@ A negative number means there were more ends than starts. "Place the cursor on the beginning of a valid comment on this line. If there isn't one, then return nil, point otherwise." (interactive) - (let ((lvl1 (matlab-compute-line-context 1))) - (goto-char (or (matlab-line-end-comment-point lvl1) - (point))))) + (let* ((lvl1 (matlab-compute-line-context 1)) + (comm (matlab-line-end-comment-point lvl1))) + (if comm + (goto-char comm) + nil))) ;;; Indent functions ========================================================== ;; diff --git a/mlint.el b/mlint.el index 3397cd3..08fe908 100644 --- a/mlint.el +++ b/mlint.el @@ -813,10 +813,14 @@ Highlight problems and/or cross-function variables." (if (not n) (message "No warning at point.") (let ((col (matlab-comment-on-line))) - (or col (end-of-line)) - (insert " %#ok") - ;; Add spaces if there was a comment. - (when col (insert " "))) + (if col + (progn + (goto-char col) + (skip-chars-forward "% ") + (insert "#ok ")) + (end-of-line) + (insert " %#ok")) + ) ;; This causes inconsistencies. ;; (linemark-delete n) )) From 194f9a986844e332be3828df0361664e0d2a64f3 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sat, 17 Apr 2021 10:38:22 -0400 Subject: [PATCH 163/168] mlint.el: (mlint-error-id-fix-alist): Add MCSCC for bad constructor name to be auto fixable. (mlint-lm-function-name::initialize-instance): Add matcher for MCSCC warnings. --- mlint.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mlint.el b/mlint.el index 08fe908..2cd3d2b 100644 --- a/mlint.el +++ b/mlint.el @@ -194,6 +194,7 @@ be cause for being turned off in a buffer." ( ENDCT2 . mlint-lm-missing-end ) ( FNDEF . mlint-lm-function-name ) ( MCFIL . mlint-lm-function-name ) + ( MCSCC . mlint-lm-function-name ) ) "List of warning IDs and auto-fix functions. If the CAR of an association matches an error id then the linemark entry @@ -562,7 +563,8 @@ Optional argument FIELDS are the initialization arguments." Optional arguments FIELDS are the initialization arguments." (let* ((warn (oref this warning)) (junk (or (string-match "file name: '\\([a-zA-z][a-zA-z0-9]+\\)'" warn) - (string-match "do not agree: '\\([a-zA-z][a-zA-z0-9]+\\)'" warn)) + (string-match "do not agree: '\\([a-zA-z][a-zA-z0-9]+\\)'" warn) + (string-match "of the subclass '\\([a-zA-z][a-zA-z0-9]+\\)'" warn)) ) (newfcn (when junk (match-string 1 warn)))) (oset this new-text newfcn) From 7dae6d85142e3ff71fe2fe6e8767c086315d3a40 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 22 Apr 2021 15:07:10 -0400 Subject: [PATCH 164/168] mlint.el: (mlint-error-id-fix-alist): Add MSNU as delete-focus auto-fix. (mlint-lm-delete-focus:: mlint-fix-entry): Also delete horizontal space if at eol. --- mlint.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mlint.el b/mlint.el index 2cd3d2b..766353e 100644 --- a/mlint.el +++ b/mlint.el @@ -188,6 +188,7 @@ be cause for being turned off in a buffer." ( NOPRT . mlint-lm-quiet ) ( NOSEM . mlint-lm-delete-focus ) ( NOCOM . mlint-lm-delete-focus ) + ( MSNU . mlint-lm-delete-focus ) ( ST2NM . mlint-lm-str2num ) ( FDEPR . mlint-lm-entry-deprecated ) ( ENDCT . mlint-lm-missing-end ) @@ -502,6 +503,8 @@ Subclasses fulfill the duty of actually fixing the code." ) (goto-char s) (delete-region (point) e) + ;; If this happened to be at end of line, just delete all left over whitespace. + (when (looking-at "\\s-*$") (delete-horizontal-space)) (point) )) ) From 66496f07a1e951b9628a659958edf6f3ae135af3 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 20 Jun 2021 08:41:36 -0400 Subject: [PATCH 165/168] matlab.el: (matlab-mode) Change default value for `add-log-current-defun-function' to be `matlab-add-log-current-defun'. (matlab-current-cell): New (matlab-add-log-current-defun): New. Use `matlab-current-defun' or above. matlab-syntax.el: (matlab-syntax-setup): Remove extra setting of page-delimiter. --- matlab-syntax.el | 2 -- matlab.el | 29 +++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/matlab-syntax.el b/matlab-syntax.el index f22b6d8..ff6a897 100644 --- a/matlab-syntax.el +++ b/matlab-syntax.el @@ -367,8 +367,6 @@ Safe to use in `matlab-mode-hook'." (setq paragraph-separate paragraph-start) (make-local-variable 'paragraph-ignore-fill-prefix) (setq paragraph-ignore-fill-prefix t) - (make-local-variable 'page-delimiter) - (setq page-delimiter "^\\(\f\\|%%\\(\\s-\\|\n\\)\\)") ;; Font lock (make-local-variable 'font-lock-syntactic-face-function) diff --git a/matlab.el b/matlab.el index ae49e25..af1e2a7 100644 --- a/matlab.el +++ b/matlab.el @@ -11,7 +11,7 @@ "Current version of MATLAB(R) mode.") ;; -;; Copyright (C) 1997-2020 Eric M. Ludlam +;; Copyright (C) 1997-2021 Eric M. Ludlam ;; Copyright (C) 1991-1997 Matthew R. Wette ;; ;; This program is free software; you can redistribute it and/or modify @@ -1340,7 +1340,7 @@ All Key Bindings: (make-local-variable 'end-of-defun-function) (setq end-of-defun-function 'matlab-skip-over-defun) (make-local-variable 'add-log-current-defun-function) - (setq add-log-current-defun-function 'matlab-current-defun) + (setq add-log-current-defun-function 'matlab-add-log-current-defun) ;; Auto-Fill and Friends (make-local-variable 'normal-auto-fill-function) @@ -1656,6 +1656,31 @@ Accounts for nested functions." ;; If that end wasn't a decl, scan upward. (matlab--scan-block-backward-up-until 'decl)))) +(defun matlab-add-log-current-defun () + "Return a text string represneting the current block. +Tries to return the current defun. If not, look for a +cell block with a name." + (or (matlab-current-defun) (matlab-current-cell))) + +(defun matlab-current-cell () + "Return the name of the current cell. +The name is any text after the %% and any whitespace." + (save-excursion + (forward-page -1) + (let ((lvl1 (matlab-compute-line-context 1)) + start) + (when (and (matlab-line-comment-p lvl1) + (eq (matlab-line-comment-style lvl1) 'cell-start)) + ;; We are in a cell start, get the content + (goto-char (matlab-line-point lvl1)) + (skip-chars-forward "% \t.,*" (point-at-eol)) + (setq start (point)) + (end-of-line 1) + (skip-chars-backward " \t*" start) + (buffer-substring-no-properties start (point)) + )) + )) + (defun matlab-current-defun () "Return the name of the current function." (save-excursion From aae075a3b0f50e49acb3d3c0c93c185b7e9f9063 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 11 Jul 2021 12:36:17 -0400 Subject: [PATCH 166/168] matlab.el: (matlab-mode-menu, matlab-mode): Remove reference to `matlab-indent-sexp' which was deleted. (matlab-function-font-lock-keywords, matlab-calculate-indentation): Improve regex for detecting end of 1st argument for functions that treat the first argument special. --- matlab.el | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/matlab.el b/matlab.el index ae49e25..7435f95 100644 --- a/matlab.el +++ b/matlab.el @@ -589,8 +589,7 @@ point, but it will be restored for them." ["Join Comment" matlab-join-comment-lines (save-excursion (matlab-comment-on-line))] ["Comment Region" matlab-comment-region t] - ["Uncomment Region" matlab-uncomment-region t] - ["Indent Syntactic Block" matlab-indent-sexp]) + ["Uncomment Region" matlab-uncomment-region t]) ("Debug" ["Edit File (toggle read-only)" matlab-shell-gud-mode-edit :help "Exit MATLAB debug minor mode to edit without exiting MATLAB's K>> prompt." @@ -1095,7 +1094,7 @@ This matcher will handle a range of variable features." (2 font-lock-reference-face)) ;; set_param and waitfor have input variables that can be highlighted. (list (concat matlab-indent-past-arg1-functions "\\s-*") - '("(\\s-*\\(\\w+\\)\\s-*\\(,\\|)\\)" nil nil + '("(\\s-*\\(\\(?:\\w\\|.\\)\\)\\s-*\\(,\\|)\\)" nil nil (1 font-lock-variable-name-face))) ) "List of font lock keywords for stuff in functions.") @@ -1266,7 +1265,6 @@ Convenient editing commands are: \\[matlab-fill-region] - Fill code and comments in region. \\[matlab-complete-symbol] - Symbol completion of matlab symbols\ based on the local syntax. - \\[matlab-indent-sexp] - Indent syntactic block of code. Convenient navigation commands are: \\[matlab-beginning-of-command] - Move to the beginning of a command. @@ -2044,7 +2042,7 @@ this line." ;; if first arg is a SIMPLE EXPR. (matlab-navigation-syntax (goto-char parenpt) - (looking-at "(\\s-*\\w+\\s-*,") + (looking-at "(\\s-*\\(?:\\w\\|\\.\\)+\\s-*,") (setq found-column (match-end 0))) (save-excursion (goto-char found-column) ; move to comma From b7e170ee37a23b6852e461772de1c4e986bb6833 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 11 Jul 2021 12:36:35 -0400 Subject: [PATCH 167/168] NEWS.org: Add version 5 news. --- NEWS.org | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/NEWS.org b/NEWS.org index 19025cc..1824484 100644 --- a/NEWS.org +++ b/NEWS.org @@ -1,5 +1,141 @@ * Changes and New Features in matlab-emacs +** New in 5.0 + +*** Syntax tables / Strings and Comments / Font lock + + Command and String syntax handling is now managed using syntax-table customization + + This results in: + * More flavors of syntax highlighting around commands and strings, including all of: + * strings, unterminated strings, commanddual strings + * comments, cellbreak comments, pragma comments, ignored comments, ellipssis + * Accurate differentiation between 'char arrays' and "strings" and quoted charts. + * Performance improvements for comment/string parsing. + + There is a new shorter 'ignore' comment type that starts with: %^ + + In addition, font lock of keywords is now more robust, with keywords not being + highlighted when they are not being used in the correct scope. + +*** Syntactic block navigation + + With proper syntax table support now available, built-in emacs commands that depend on + sexp now work, such as: + * up-list + * forward-sexp + * kill-sexp + * mark-sexp + + In addition, commands that work with defuns now all work correctly, such as: + * mark-defun + * narrow-to-defun + + All custom commands that used to implement these syntax behaviors have been removed, or + had their bindings removed, including: + * matlab-beginning-of-command + * matlab-end-of-command + * matlab-forward-sexp + * matlab-backward-sexp + * matlab-indent-sexp + * matlab-beginning-of-defun + * matlab-end-of-defn + + In addition syntactic block navigation is faster, where very large files can now be navigated + in fractions of a second that used to take a few minutes. + +*** Support for block validation + + Block navigation now does validation, for example, 'property' keywords should only occur + inside a classdef, and 'arguments' keywords should only occur inside a function. + + This means that you can now have variables and functions named 'property' and + 'arguments' of those words occur outside valid locations. + +*** Indentation + + Indentation performance is greatly improved. Based on our tests, large files that used + to take 10 minutes to indent will now complete in just 1 or 2 seconds. + + Several new indentation features exist, such as: + * correct indentation of arguemnts blocks + * improved indentation of function argument lists that span multiple lines. + * improved indentation around block comments + * improved indentation accuracy in classdef, property, method blocks. + * more accurate indentation of continuations + + Some indentation features were removed, such as: + * Max indent distance support inside function call args + * Max indent distance support inside switch statements + * Line-up rules inside ( ), [ ], and { } have changed subtly dependeing on + context after the opening (, [, or {. + + Specialty indentation commands have been removed: + * matlab-indent-sexp + + Electric indentation has been added to block keywords such as end, else, case, etc. + + Lots of bug fixes and general improvements for handling edge cases. + +*** matlab-return & friends removed + + The 'matlab-return' and related functions have all been removed. Features of these + commands are now part of Emacs' built in handling for RETURN and no longer need to be + part of matlab mode. + +*** File type detection + + File type detection has been improved. Previously matlab mode detected if functions had + ends, and if functions were indented. It now detects more cases, and displays results + in the status line. + + The list of detectable features are: + * function (with no end) + * function .. end + * classdef .. end + * scripts + * empty files + + Functions with ends also detect if function bodies are indented. Other kinds of + functions will always indent. + + The check for the type of file is also auto-re-detected on save, so if you change the + type of file while editing, it will automatically adjust. + +*** Auto verify changes + + Auto verify on save has been updated. + + 1. verify classname added - this will fix class names for you + 2. verify add ends - this now asks questions less often, or not at all. + In addition, it has a more robust algorithm for adding ends. + +*** mlint support questions + + mlint mode now supports many more auto fix behaviors, including: + * missing ends - with nicer guess for where the end goes. + * function name - auto fix function, class, and method names. + + Plus several minor bug fixes. + +*** Support for older Emacsen + + Support for Emacs older than Emacs 24 has been dropped. Many of the special + compatability layers needed to change, and the new code has not been tested against + older versions of Emacs. As a result, many compatability layers were removed. + +*** Test suite improvements + + The test suite that is part of matlab-emacs project has many more test points, and has + added support for testing font lock, performance, and other features. + +*** matlab-emacs maintenance mode + +There is now a support file 'matlab-maint' that simplifies the task of building and +testing matlab-mode during development. Consider using this library if you intend to +develop matlab-mode. + + ** News in 4.0 *** Debugging @@ -56,7 +192,7 @@ You have the following capabilities when in a MATLAB M-file: more responsive. For example, in matlab-shell, Emacs is more responsive when processing long output lines from MATLAB. -*** Bug fixex +*** Bug fixes - There are a number of bug fixes. *** Quotes From 4facc0211600afc211a747398db22f54216cf71d Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Sun, 11 Jul 2021 12:45:08 -0400 Subject: [PATCH 168/168] matlab.el: (matlab-function-font-lock-keywords): Fix bug in last submit so tests pass. --- matlab.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matlab.el b/matlab.el index 6e9c92b..9eb7aa7 100644 --- a/matlab.el +++ b/matlab.el @@ -1094,7 +1094,7 @@ This matcher will handle a range of variable features." (2 font-lock-reference-face)) ;; set_param and waitfor have input variables that can be highlighted. (list (concat matlab-indent-past-arg1-functions "\\s-*") - '("(\\s-*\\(\\(?:\\w\\|.\\)\\)\\s-*\\(,\\|)\\)" nil nil + '("(\\s-*\\(\\(?:\\w\\|\\.\\)+\\)\\s-*\\(,\\|)\\)" nil nil (1 font-lock-variable-name-face))) ) "List of font lock keywords for stuff in functions.")