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

    Update imenu to use matlab syntax font-lock
    
    The regular expression needed for imenu causes the regex engine to hang.  
Thus, use
    imenu-create-index-function and leverage matlab syntax font-lock to 
identify the function names for
    imenu.
    
    Also added tip in doc/matlab-imenu.org on how add a Fcns menu to Emacs for 
these.
    
    See: https://github.com/mathworks/Emacs-MATLAB-Mode/issues/42
---
 doc/matlab-imenu.org                            |  18 +++-
 matlab.el                                       | 127 +++++++++---------------
 tests/metest-imenu-files/f0_expected.txt        |   2 +
 tests/metest-imenu-files/g0_expected.txt        |   3 +-
 tests/metest-imenu-files/oneliner.m             |   1 +
 tests/metest-imenu-files/oneliner_expected.txt  |   1 +
 tests/metest-imenu-files/symPosDef.m            |  19 ++++
 tests/metest-imenu-files/symPosDef_expected.txt |   3 +
 tests/metest-imenu.el                           |  47 ++++-----
 9 files changed, 113 insertions(+), 108 deletions(-)

diff --git a/doc/matlab-imenu.org b/doc/matlab-imenu.org
index 02ceb81e20..b671bf5ca6 100644
--- a/doc/matlab-imenu.org
+++ b/doc/matlab-imenu.org
@@ -5,8 +5,9 @@
 
 # Copyright 2025 Free Software Foundation, Inc.
 
-matlab-mode provides 
[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html][imenu]] 
support which lets you jump quickly to functions in the current ~*.m~
-file you are visiting.  Typing ~M-g i~ (or ~M-x imenu~) will prompt you in the 
mini-buffer:
+matlab-mode and tlc-mode provides 
[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html][imenu]] 
support which lets you jump quickly to functions in the
+current ~*.m~ file you are visiting.  Typing ~M-g i~ (or ~M-x imenu~) will 
prompt you in the
+mini-buffer:
 
  : Index item:
 
@@ -49,3 +50,16 @@ gives:
 #+end_example
  
 and you can select the one you'd like by clicking on it or by typing the name 
with tab completion.
+
+** Fcns Menu
+
+To add a "Fcns" menu to Emacs for MATLAB and/or TLC files.
+
+ : M-x customize-variable RET matlab-mode-hook RET
+ : M-x customize-variable RET tlc-mode-hook RET
+
+and add:
+
+#+begin_src emacs-lisp
+   (lambda () (imenu-add-to-menubar "Fcns"))
+#+end_src
diff --git a/matlab.el b/matlab.el
index 4a8f391714..04580a8282 100644
--- a/matlab.el
+++ b/matlab.el
@@ -199,6 +199,47 @@ If the value is \\='guess, then we guess if a file has end 
when
 (make-variable-buffer-local 'matlab-functions-have-end)
 (put 'matlab-functions-have-end 'safe-local-variable #'symbolp)
 
+(defun matlab--imenu-index ()
+  "Return index for imenu.
+This will return alist of functions in the current *.m file:
+   \\='((\"function1\" . start-point1)
+     (\"function2\" . start-point2)
+This searches for `font-lock-function-name-face' font-lock property to
+locate the functions."
+
+  ;; Handle case of font-lock being out of date.
+  (font-lock-mode 1)
+  (font-lock-fontify-region (point-min) (point-max))
+
+  (goto-char (point-min))
+  (let (match
+       (index '())
+        last-end-pt)
+    (while (setq match (text-property-search-forward
+                       'face 'font-lock-function-name-face
+                       (lambda (value prop)
+                         (or (eq prop value)
+                             (and (listp prop)
+                                  (member value prop))))))
+      (let* ((start-pt (prop-match-beginning match))
+            (end-pt (prop-match-end match))
+            (function-name (buffer-substring start-pt end-pt)))
+
+        (if (and last-end-pt
+                 (= last-end-pt start-pt))
+            ;; classdef get and set methods have multiple faces, so join (1) 
and (2):
+            ;;     function obj = set.inputMatrix(obj,val)
+            ;;                    ^---^----------
+            ;;                    (1) (2)
+            (let* ((last-el (car index))
+                   (last-function-name (car last-el))
+                   (last-start-pt (cdr last-el))
+                   (new-el (cons (concat last-function-name function-name) 
last-start-pt)))
+              (setcar index new-el))
+         (push `(,function-name . ,start-pt) index))
+        (setq last-end-pt end-pt)))
+    (reverse index)))
+
 (defun matlab-toggle-functions-have-end ()
   "Toggle `matlab-functions-have-end-minor-mode'."
   (interactive)
@@ -406,14 +447,6 @@ This is used to generate and identify continuation lines."
   :group 'matlab
   :type 'string)
 
-(defvar matlab--ellipsis-to-eol-re
-  (concat "\\.\\.\\.[[:blank:]]*\\(?:%[^\r\n]*\\)?\r?\n")
-  "Regexp used to match either of the following including the newline.
-For example, the end-of-lines:
-  ...
-  ... % comment
-are matched.")
-
 (defcustom matlab-fill-code nil
   "*If true, `auto-fill-mode' causes code lines to be automatically continued."
   :group 'matlab
@@ -1217,77 +1250,6 @@ This matcher will handle a range of variable features."
   "Expressions to highlight in MATLAB mode.")
 
 
-;; -----------------
-;; | Imenu support |
-;; -----------------
-;; Example functions we match, f0, f1, f2, f3, f4, f5, F6, g4
-;;    function f0
-;;    function...
-;;        a = f1
-;;    function f2
-;;    function x = ...
-;;            f3
-;;    function [a, ...
-;;              b ] ...
-;;              = ...
-;;              f4(c)
-;;    function a = F6
-;;    function [ ...
-;;        a, ... % comment for a
-;;        b  ... % comment for b
-;;             ] ...
-;;             = ...
-;;             g4(c)
-;;
-(defvar matlab-imenu-generic-expression
-  ;; Using concat to increase indentation and improve readability
-  `(,(list nil (concat
-                "^[[:blank:]]*"
-                "function\\>"
-
-                ;; Optional return args, function ARGS = NAME. Capture the 
'ARGS ='
-
-                ;; The following regexp is better for capturing 'ARGS ='.  The 
regexp allows for any
-                ;; characters in the "% comments", however with Emacs 30.1, 
running:
-                ;;    M-: (re-search-forward (cadar 
matlab-imenu-generic-expression) nil t)
-                ;; on this file with point at 0
-                ;;     function 
foo123567890123567890123567890123567890123567890(fh)
-                ;;     end
-                ;; gives us a hang. If you shorten the function name, Emacs 
won't hang.
-                ;;
-                ;; (concat "\\(?:"
-                ;;         ;; ARGS can span multiple lines
-                ;;         (concat "\\(?:"
-                ;;                 ;; valid ARGS chars: "[" "]" variables "," 
space, tab
-                ;;                 "[]\\[a-zA-Z0-9_,[:blank:]]*"
-                ;;                 ;; Optional continue to next line "..." or 
"... % comment"
-                ;;                 "\\(?:" matlab--ellipsis-to-eol-re "\\)?"
-                ;;                 "\\)+")
-                ;;         ;; ARGS must be preceeded by the assignment 
operator, "="
-                ;;         "[[:blank:]]*="
-                ;;        "\\)?")
-
-                ;; Capture 'ARGS = ' using a less accurate regexp that doesn't 
handle ellipsis
-                ;; due to performance problems with the above.
-                (concat "\\(?:"
-                        ;; valid ARGS chars: "[" "]" variables "," space, tab
-                        "[]\\[a-zA-Z0-9_,[:blank:]]+"
-                        ;; ARGS must be preceeded by the assignment operator, 
"="
-                        "="
-                        "\\)?")
-
-                ;; Optional space/tabs, "...", or "... % comment" continuation
-                (concat "\\(?:"
-                        "[[:blank:]]*"
-                        "\\(?:" matlab--ellipsis-to-eol-re "\\)?"
-                        "\\)*")
-
-                "[\\.[:space:]\n\r]*"
-                "\\([a-zA-Z][a-zA-Z0-9_]+\\)" ;; function NAME
-                )
-           1))
-  "Regexp to find function names in *.m files for `imenu'.")
-
 
 ;;; MATLAB mode entry point ==================================================
 
@@ -1447,8 +1409,9 @@ All Key Bindings:
   (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)
+
+  ;; Imenu
+  (setq-local imenu-create-index-function #'matlab--imenu-index)
 
   ;; Save hook for verifying src.  This lets us change the name of
   ;; the function in `write-file' and have the change be saved.
@@ -3224,7 +3187,7 @@ desired."
 ;; LocalWords:  keymap setq decl memq classdef's progn mw vf functionname 
booleanp torkel fboundp
 ;; LocalWords:  gud ebstop mlgud ebclear ebstatus mlg mlgud's subjob featurep 
defface commanddual
 ;; LocalWords:  docstring cdr animatedline rlim thetalim cartesian stackedplot 
bubblechart
-;; LocalWords:  swarmchart wordcloud bubblecloud heatmap parallelplot fcontour 
anim polarplot Imenu
+;; LocalWords:  swarmchart wordcloud bubblecloud heatmap parallelplot fcontour 
anim polarplot
 ;; LocalWords:  polarscatter polarhistogram polarbubblechart goeplot 
geoscatter geobubble geodensity
 ;; LocalWords:  fimplicit fsurf tiledlayout nexttile uicontext mld flintmax 
keywordlist mapconcat
 ;; LocalWords:  vardecl flb fle tmp blockmatch md tm newmdata repeat:md sw 
imenu boundp aref alist
diff --git a/tests/metest-imenu-files/f0_expected.txt 
b/tests/metest-imenu-files/f0_expected.txt
index f35cfaccdc..251b035c72 100644
--- a/tests/metest-imenu-files/f0_expected.txt
+++ b/tests/metest-imenu-files/f0_expected.txt
@@ -1,6 +1,8 @@
 f0
+f1
 f2
 f3
+f4
 f5
 F6
 f7
diff --git a/tests/metest-imenu-files/g0_expected.txt 
b/tests/metest-imenu-files/g0_expected.txt
index df8fd65723..40f06c0373 100644
--- a/tests/metest-imenu-files/g0_expected.txt
+++ b/tests/metest-imenu-files/g0_expected.txt
@@ -1,4 +1,5 @@
 g0
-g2
+g1
 g3
+g4
 g5
diff --git a/tests/metest-imenu-files/oneliner.m 
b/tests/metest-imenu-files/oneliner.m
new file mode 100644
index 0000000000..e5df49b4ac
--- /dev/null
+++ b/tests/metest-imenu-files/oneliner.m
@@ -0,0 +1 @@
+function oneliner, x = abc; end
diff --git a/tests/metest-imenu-files/oneliner_expected.txt 
b/tests/metest-imenu-files/oneliner_expected.txt
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/tests/metest-imenu-files/oneliner_expected.txt
@@ -0,0 +1 @@
+
diff --git a/tests/metest-imenu-files/symPosDef.m 
b/tests/metest-imenu-files/symPosDef.m
new file mode 100644
index 0000000000..ebbd141aff
--- /dev/null
+++ b/tests/metest-imenu-files/symPosDef.m
@@ -0,0 +1,19 @@
+classdef symPosDef
+    properties
+        inputMatrix = [1 0; 0 1]
+    end
+    
+    methods
+        function obj = set.inputMatrix(obj,val)
+            try chol(val)
+                obj.inputMatrix = val;
+            catch ME
+                error("inputMatrix must be symmetric positive definite.")
+            end
+        end
+
+        function m = get.inputMatrix(obj)
+            m = obj.inputMatrix;
+        end
+    end
+end
diff --git a/tests/metest-imenu-files/symPosDef_expected.txt 
b/tests/metest-imenu-files/symPosDef_expected.txt
new file mode 100644
index 0000000000..458fbd8fa5
--- /dev/null
+++ b/tests/metest-imenu-files/symPosDef_expected.txt
@@ -0,0 +1,3 @@
+symPosDef
+set.inputMatrix
+get.inputMatrix
diff --git a/tests/metest-imenu.el b/tests/metest-imenu.el
index 688520b5e4..d73dc28f61 100644
--- a/tests/metest-imenu.el
+++ b/tests/metest-imenu.el
@@ -55,31 +55,32 @@ For debugging, you can run with a specified M-FILE,
       (save-excursion
         (message "START: (metest-imenu \"%s\")" m-file)
 
-        (find-file m-file)
-        (goto-char (point-min))
+        (let ((m-file-buf (find-file m-file)))
+          (with-current-buffer m-file-buf
 
-        (let* ((imenu-re (cadar matlab-imenu-generic-expression))
-               (got "")
-               (expected-file (replace-regexp-in-string "\\.m$" 
"_expected.txt" m-file))
-               (got-file (concat expected-file "~"))
-               (expected (when (file-exists-p expected-file)
-                           (with-temp-buffer
-                             (insert-file-contents-literally expected-file)
-                             (buffer-string))))
-               (case-fold-search nil))
-          (while (re-search-forward imenu-re nil t)
-            (setq got (concat got (match-string 1) "\n")))
+            (let* ((index (matlab--imenu-index))
+                   (got (concat (string-join
+                                 (mapcar (lambda (el) (substring-no-properties 
(car el))) index)
+                                 "\n")
+                                "\n"))
+                   (expected-file (replace-regexp-in-string "\\.m$" 
"_expected.txt" m-file))
+                   (got-file (concat expected-file "~"))
+                   (expected (when (file-exists-p expected-file)
+                               (with-temp-buffer
+                                 (insert-file-contents-literally expected-file)
+                                 (buffer-string)))))
 
-          (when (not (string= got expected))
-            (let ((coding-system-for-write 'raw-text-unix))
-              (write-region got nil got-file))
-            (when (not expected)
-              (error "Baseline for %s does not exists.  See %s and if it looks 
good rename it to %s"
-                     m-file got-file expected-file))
-            (error "Baseline for %s does not match, got: %s, expected: %s"
-                   m-file got-file expected-file))
-          (kill-buffer)))
-      (message "PASS: (metest-imenu \"%s\")" m-file)))
+              (when (not (string= got expected))
+                (let ((coding-system-for-write 'raw-text-unix))
+                  (write-region got nil got-file))
+                (when (not expected)
+                  (error "Baseline for %s does not exist.
+See %s and if it looks good rename it to %s"
+                         m-file got-file expected-file))
+                (error "Baseline for %s does not match, got: %s, expected: %s"
+                       m-file got-file expected-file))))
+          (kill-buffer m-file-buf))
+        (message "PASS: (metest-imenu \"%s\")" m-file))))
   "success")
 
 (provide 'metest-imenu)

Reply via email to