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:")