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

    matlab-shell: handle case insensitive TAB completions
    
    Some MATLAB completions are case insensitive. Consider:
        set_param(bdroot, 'simulationc<TAB>
    and this now completes to
        set_param(bdroot, 'SimulationCommand
---
 Makefile                    |  2 +-
 matlab-shell.el             | 38 ++++++++++++++----
 tests/mstest-completions.el | 98 +++++++++++++++++++++++++++++++++++++++++++++
 tests/mstest.el             | 45 ++-------------------
 4 files changed, 134 insertions(+), 49 deletions(-)

diff --git a/Makefile b/Makefile
index 7f32e9b9f0..e3278749cc 100644
--- a/Makefile
+++ b/Makefile
@@ -107,7 +107,7 @@ clean:
 #
 # This requires that you define EMACS27, etc. in the environment or specify 
them when invoking make.
 
-SUPPORTED_EMACS_VERSIONS = 27 28 29
+SUPPORTED_EMACS_VERSIONS = 27 28 29 30
 
 define CHECK_EMACS_VER
   ifeq ($$(shell which $(EMACS${1})),)
diff --git a/matlab-shell.el b/matlab-shell.el
index 4d6a016016..6e9f89a81a 100644
--- a/matlab-shell.el
+++ b/matlab-shell.el
@@ -1417,7 +1417,10 @@ is the common starting substring of each completion in 
completions."
              (chomp-num-chars nil))
         (while (> i 0)
           (let ((part (substring completion 0 i)))
-            (if (string-suffix-p part last-cmd)
+            ;; Do case insensitive comparison on the substring suffix.  
Consider
+            ;;    set_param(bdroot, 'simulationc<TAB>
+            ;; which gives completions == '(("SimulationCommand"))
+            (if (string-suffix-p part last-cmd t)
                 (progn
                   (setq chomp-num-chars i)
                   (setq i 0))
@@ -1504,11 +1507,32 @@ No completions are provided anywhere else in the 
buffer."
         (re-search-forward comint-prompt-regexp)
         (setq common-substr-start-pt (+ (point) limit-pos))
         (setq common-substr-end-pt (line-end-position))
-        (if (and (eq (length completions) 1)
-                 (string-equal (buffer-substring-no-properties
-                                common-substr-start-pt common-substr-end-pt)
-                               (car (car completions))))
-            (setq completions nil))) ;; force display of "No completions"
+
+        ;; Some MATLAB completions are case insensitive. Consider:
+        ;;    set_param(bdroot, 'simulationc<TAB>
+        ;; We'll get completions == '(("SimulationCommand")) and the 
common-substr
+        ;; ignoring case will be "simulationc" whereas the common substring in 
completions
+        ;; is "SimulationC". In this case replace "simulationc" with 
"SimulationC" for the
+        ;; completion engine and after TAB completion completes, we'll see
+        ;;    set_param(bdroot, 'SimulationCommand
+        (when (and (< common-substr-start-pt common-substr-end-pt)
+                   (> (length completions) 0))
+          (let* ((common-substr-len (- common-substr-end-pt 
common-substr-start-pt))
+                 (c-common-substr (substring (caar completions) 0 
common-substr-len)))
+
+            (when (and (not (string-equal c-common-substr common-substr))
+                       ;; compare-strings case insensitive
+                       (eq t (compare-strings c-common-substr 0 nil 
common-substr 0 nil t)))
+              (save-excursion
+                (delete-region common-substr-start-pt common-substr-end-pt)
+                (goto-char common-substr-start-pt)
+                (insert c-common-substr)))))
+
+        ;; If completion is same as what we have, then it's not a completion
+        (when (and (eq (length completions) 1)
+                   (string-equal common-substr (car (car completions))))
+          (setq completions nil)) ;; force display of "No completions"
+        )
       ;; Result
       (list (cons 'last-cmd               last-cmd)
             (cons 'common-substr          common-substr)
@@ -2694,4 +2718,4 @@ Argument FNAME specifies if we should echo the region to 
the command line."
 ;; LocalWords:  BUILTINFLAG dired bol bobp numberp princ minibuffer fn 
matlabregex lastcmd notimeout
 ;; LocalWords:  stacktop eltest testme localfcn LF fileref funcall ef ec basec 
sk nondirectory utils
 ;; LocalWords:  ignoredups boundp edir sexp Fixup mapc emacsrun noshow cnt 
ellipsis newf bss noselect
-;; LocalWords:  fname mlx xemacs linux darwin truename clientcmd
+;; LocalWords:  fname mlx xemacs linux darwin truename clientcmd simulationc 
caar
diff --git a/tests/mstest-completions.el b/tests/mstest-completions.el
new file mode 100644
index 0000000000..d126683e3c
--- /dev/null
+++ b/tests/mstest-completions.el
@@ -0,0 +1,98 @@
+;;; mstest-completions.el --- MATLAB Shell sections tests -*- lexical-binding: 
t; -*-
+
+;; Copyright 2019-2025 Free Software Foundation, Inc.
+;; Author: John Ciolfi <john.ciolfi...@gmail.com>, Eric Ludlam <zappo@ballista>
+
+;; 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:
+;;    Test matlab-shell completions
+
+;;; Code:
+
+(require 'matlab-shell)
+
+(declare-function mstest-savestate "mstest.el")
+
+(defun mstest-completion-test-point (test-point-desc cmd expected)
+  "Run TEST-POINT-DESC tab completion on CMD and compare against EXPECTED.
+
+*MATLAB* buffer should be current."
+  (goto-char (point-max))
+  (message "TEST: %s" test-point-desc)
+  (let* ((CLO
+          (condition-case ERR
+              (matlab-shell-completion-list cmd)
+            (error
+             (mstest-savestate)
+             (user-error "%S" ERR))))
+         (CL (cdr (nth 2 CLO)))
+         (cnt 1))
+    (while (and CL expected)
+      (when (not (string= (car expected) (car (car CL))))
+        (mstest-savestate)
+        (user-error "Expected %S /= %S TS for %d completion"
+                    (car expected) (car (car CL)) cnt))
+            (setq cnt (1+ cnt)
+                  CL (cdr CL)
+                  expected (cdr expected))))
+  (message "PASS: %s" test-point-desc))
+
+(defun mstest-completion ()
+  "Test emacsdocomplete and verifies result."
+  (let ((msb (matlab-shell-active-p)))
+    (when (not msb)
+      (user-error "Test, mstest-completion, must run after mstest-start"))
+
+    (with-current-buffer msb
+      (mstest-completion-test-point "mstest-completion: emacs<TAB>"
+                                    "emacs"
+                                    '("emacs"
+                                      "emacscd"
+                                      "emacsdocomplete"
+                                      "emacsinit"
+                                      "emacsnetshell"
+                                      "emacsrun"
+                                      "emacsrunregion"
+                                      "emacsstripremote"
+                                      "emacstipstring"))
+
+      ;; When Simulink is installed, test completion where we need to change 
case of command in the
+      ;; matlab-shell buffer.
+      (let* ((cmd "set_param('untitledTabTest', 'simulationcomma")
+             (test-point-desc (format "mstest-completion: %s<TAB>" cmd)))
+        (when (file-exists-p (concat (matlab-shell-matlabroot)
+                                     
"/toolbox/simulink/blocks/library/simulink.slx"))
+          (message "TEST: %s" test-point-desc)
+          (goto-char (point-max))
+          (message "starting Simulink: new_system('untitledTabTest')")
+          (matlab-shell-collect-command-output
+           "new_system('untitledTabTest'); disp('new_sys')")
+          (insert "set_param('untitledTabTest', 'simulationcomma")
+          (goto-char (point-max))
+          (matlab-shell-tab)
+          (goto-char (point-max))
+          (beginning-of-line)
+          (when (not (looking-at "set_param('untitledTabTest', 
'SimulationCommand"))
+            (mstest-savestate)
+            (user-error "FAILED %s" test-point-desc))
+          (kill-line)
+          (matlab-shell-collect-command-output
+           "close_system('untitledTabTest', 0); disp('close_sys')")))
+      ))
+  ;; Indicate success. Useful when debugging.
+  t)
+
+(provide 'mstest-completions)
+;;; mstest-completions.el ends here (emacs-lisp-checkdoc)
diff --git a/tests/mstest.el b/tests/mstest.el
index ed18c4126e..1c379ca2bb 100644
--- a/tests/mstest.el
+++ b/tests/mstest.el
@@ -41,6 +41,7 @@
 (require 'matlab-topic)
 
 (add-to-list 'load-path ".")
+(require 'mstest-completions)
 (require 'mstest-sections)
 
 ;;; Code:
@@ -158,44 +159,6 @@
       (goto-char (point-max)))))
 
 ;;; Command Sending Tests
-(defun mstest-completion ()
-  "Test emacsdocomplete and verifies result."
-  (let ((msb (matlab-shell-active-p)))
-    (when (not msb)
-      (user-error "Test, mstest-completion, must run after mstest-start"))
-
-    (with-current-buffer msb
-      (goto-char (point-max))
-
-      ;; TEST completion fcn
-      (message "COMPLETION TEST: emacs")
-      (let* ((CLO
-              (condition-case ERR
-                  (matlab-shell-completion-list "emacs")
-                (error
-                 (mstest-savestate)
-                 (user-error "%S" ERR))))
-             (CL (cdr (nth 2 CLO)))
-             (EXP '("emacs"
-                    "emacscd"
-                    "emacsdocomplete"
-                    "emacsinit"
-                    "emacsnetshell"
-                    "emacsrun"
-                    "emacsrunregion"
-                    "emacsstripremote"
-                    "emacstipstring"))
-             (cnt 1))
-        (while (and CL EXP)
-          (when (not (string= (car EXP) (car (car CL))))
-            (user-error "Expected %S /= %S TS for %d completion"
-                        (car EXP) (car (car CL)) cnt))
-          (setq cnt (1+ cnt)
-                CL (cdr CL)
-                EXP (cdr EXP))))
-      (message "PASS")
-
-      )))
 
 (defvar mstest-EVAL-TEST)
 
@@ -452,7 +415,7 @@ If LINE is negative then do not test the line number."
           (mstest-savestate)
           (message "DEBUG: txt = %S" txt)
           (user-error "DEBUG: Stack buffer did not contain stack frame for %S, 
found [%s]"
-                      SK (buffer-substring (point-at-bol) (point-at-eol))))
+                      SK (buffer-substring (line-beginning-position) 
(line-end-position))))
         (forward-line 1)
         (setq cnt (1+ cnt)))
 
@@ -479,7 +442,7 @@ If LINE is negative then do not test the line number."
           (mstest-savestate)
           (message "DEBUG: txt=%S" txt)
           (user-error "DEBUG: Breakpoints buffer did not contain breakpoint 
for %S, found [%s]"
-                      BP (buffer-substring (point-at-bol) (point-at-eol))))
+                      BP (buffer-substring (line-beginning-position) 
(line-end-position))))
         (forward-line 1)
         (setq cnt (1+ cnt)))
 
@@ -640,7 +603,7 @@ set in the same order as specified."
 
 (defun mstest-org-next-code-block-results (action)
   "Perform ACTION on the next #+RESULTS area of an Org code block.
-ACTION can be 'delete or 'get."
+ACTION can be \\='delete or \\='get."
   (save-excursion
     (let* ((results-begin (save-excursion
                             (re-search-forward "^[ \t]*#\\+RESULTS:")

Reply via email to