From c12e9bcc65f30f2a3c212d0ab8fb35fb71d9d718 Mon Sep 17 00:00:00 2001 From: Eric Ludlam Date: Thu, 25 Feb 2021 21:10:20 -0500 Subject: [PATCH] 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)