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.
This commit is contained in:
Eric Ludlam 2021-03-26 21:05:11 -04:00
parent 4c389c08fa
commit 67eb90e872
12 changed files with 123 additions and 145 deletions

View File

@ -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.)"

210
matlab.el
View File

@ -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?
'("\\([<>~=]=\\|\\.[/\\*^'?]\\|\\_<\\(?:\\<xor\\|any\\|all\\|find\\)\\_>\\|[-<>!?^&|*+\\/~:@]\\)"
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)

View File

@ -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))

View File

@ -1,2 +1,2 @@
% Empty M file.
% %%%empty guess
% %%%empty guess guess

View File

@ -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#

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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

15
tests/mfuncnoendindent.m Normal file
View File

@ -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

View File

@ -3,7 +3,7 @@
%
% Used with test harness to validate indentation and end detection.
%
% %%%function function
% %%%function function function
fcn_call();

View File

@ -2,7 +2,7 @@
%
% #c#
%
% %%%script script
% %%%script script script
%% Basic strings