branch: externals/matlab-mode commit 5428ad383b72eda1846647c4fc605b05d0e21517 Author: John Ciolfi <john.ciolfi...@gmail.com> Commit: John Ciolfi <john.ciolfi...@gmail.com>
matlab.el: fix incorrect font in classdef property comments In matlab-font-lock-anchor-variable-match function, skip over comments thus ensuring comments retain the comment face. See https://github.com/mathworks/Emacs-MATLAB-Mode/issues/31 --- matlab.el | 55 ++++++--- tests/metest-font-lock-test2-files/basic1.m | 8 ++ .../basic1_expected.txt | 8 ++ tests/metest-font-lock-test2-files/basic2.m | 5 + .../basic2_expected.txt | 5 + tests/metest-font-lock-test2-files/foo.m | 3 + .../metest-font-lock-test2-files/foo_expected.txt | 3 + tests/metest-font-lock-test2.el | 131 +++++++++++++++++++++ tests/metest.el | 4 + 9 files changed, 206 insertions(+), 16 deletions(-) diff --git a/matlab.el b/matlab.el index 7772eab202..7935a4dfc6 100644 --- a/matlab.el +++ b/matlab.el @@ -987,27 +987,51 @@ color support." "Clear the end limit for anchored matchers." (setq ml-fl-anchor-limit nil)) +(defun matlab--move-to-next-language-element () + "Move point over comments and whitespace to next language element. +If point is on a language element, e.g. a variable name, no movement +occurs." + (let ((start-point (1- (point)))) + (while (< start-point (point)) + (setq start-point (point)) + (forward-comment 1)))) + (defun matlab-font-lock-anchor-variable-match (limit) "After finding a keyword like PROPERTIES or ARGUMENTS, match vars. LIMIT is the search limit. This matcher will handle a range of variable features." - (ignore limit) + ;; limit seems to always match ml-fl-anchor-limit, but take min to be sure + (setq limit (min limit ml-fl-anchor-limit)) (when (member (nth 1 matlab-fl-anchor-keyword) '("properties" "events" "arguments")) - (let* ((match (re-search-forward "\\(?:^\\|[,;]\\)\\s-+\\(\\(?:\\w+\\|\\.\\)+\\)\\_>" ml-fl-anchor-limit t)) - ;; Save this match so we can do a 2nd anchored search for a data type. - (md1 (list (match-beginning 1) (match-end 1))) - (tm (looking-at - "\\(\\(?:\\s-*([^\n\)]+)\\s-*\\|\\s-+\\)?\\(?:\\w+\\(?:\\.\\w+\\)*\\)?\\)\\s-*\\($\\|[;%{=]\\)")) - (tm1 (if tm (list (match-beginning 1) (match-end 1)) - ;; The below is a cheat to not highlight anything but - ;; still supply the match data for this optional piece. - (list (nth 1 md1) (nth 1 md1)))) - (newmdata (append md1 md1 tm1))) - (when match - (goto-char (line-end-position)) - (set-match-data newmdata) - t)))) + (let ((start-point (point))) + + ;; Skip over comments so that our regex matchers below do not find items in them. + (matlab--move-to-next-language-element) + + (when (< (point) limit) + (let ((match (progn + ;; When we skip over comments, we need to move back to the start of the line + ;; to ensure our "^" in the regex matches when needed. + (goto-char (max start-point (line-beginning-position))) + (re-search-forward "\\(?:^\\|[,;]\\)\\s-*\\(\\(?:\\w+\\|\\.\\)+\\)\\_>" + limit t)))) + (when match + (let* ( + ;; Save this match so we can do a 2nd anchored search for a data type. + (md1 (list (match-beginning 1) (match-end 1))) + (tm (looking-at + "\\(\\(?:\\s-*([^\n)]+)\\s-*\\|\\s-+\\)?\\(?:\\w+\\(?:\\.\\w+\\)*\\)?\\)\\s-*\\($\\|[;%{=]\\)")) + (tm1 (if tm + (list (match-beginning 1) (match-end 1)) + ;; The below is a cheat to not highlight anything but + ;; still supply the match data for this optional piece. + (list (nth 1 md1) (nth 1 md1)))) + (newmdata (append md1 md1 tm1))) + (goto-char (line-end-position)) + (set-match-data newmdata) + ;; return t which tells font-lock we matched + t))))))) ;;; Font Lock keyword handling ;; @@ -3151,4 +3175,3 @@ desired." ;; LocalWords: parenpt parenindent parenopt FUNCTION's EOL depthchange bc eol fn emacsen afterd ;; LocalWords: befored okpos startlst endlst ellipsify ppss noreturn hs tc hc startsym endsym mapc ;; LocalWords: func bn nondirectory scanstate sexp's nosemi msgpos nexti defn - diff --git a/tests/metest-font-lock-test2-files/basic1.m b/tests/metest-font-lock-test2-files/basic1.m new file mode 100644 index 0000000000..67468e08ba --- /dev/null +++ b/tests/metest-font-lock-test2-files/basic1.m @@ -0,0 +1,8 @@ +classdef basic1 + properties (Access = public) + % aa1, bb1 cc1 + aaa = 0 + % aa2, bb2 + MyPublicData (1,:) double {mustBePositive} = [1 1 1] + end +end diff --git a/tests/metest-font-lock-test2-files/basic1_expected.txt b/tests/metest-font-lock-test2-files/basic1_expected.txt new file mode 100644 index 0000000000..20d376c582 --- /dev/null +++ b/tests/metest-font-lock-test2-files/basic1_expected.txt @@ -0,0 +1,8 @@ +kkkkkkkk ffffff + kkkkkkkkkk dtttttt d ddddddd + C cccc ccc ccc + vvv d d + C cccc ccc + vvvvvvvvvvvv ttttt tttttt dddddddddddddddd d dd d dd + kkk +kkk diff --git a/tests/metest-font-lock-test2-files/basic2.m b/tests/metest-font-lock-test2-files/basic2.m new file mode 100644 index 0000000000..2520ee277a --- /dev/null +++ b/tests/metest-font-lock-test2-files/basic2.m @@ -0,0 +1,5 @@ +function events=basic2(arguments) + arguments, + arguments(:,:) {mustBeNumeric} + end +end diff --git a/tests/metest-font-lock-test2-files/basic2_expected.txt b/tests/metest-font-lock-test2-files/basic2_expected.txt new file mode 100644 index 0000000000..7f871f9e95 --- /dev/null +++ b/tests/metest-font-lock-test2-files/basic2_expected.txt @@ -0,0 +1,5 @@ +kkkkkkkk vvvvvvdffffffdvvvvvvvvvd + kkkkkkkkkd + vvvvvvvvvttttt dtttttttttttttd + kkk +kkk diff --git a/tests/metest-font-lock-test2-files/foo.m b/tests/metest-font-lock-test2-files/foo.m new file mode 100644 index 0000000000..4e3755e286 --- /dev/null +++ b/tests/metest-font-lock-test2-files/foo.m @@ -0,0 +1,3 @@ +function a = foo + a = 1; +end diff --git a/tests/metest-font-lock-test2-files/foo_expected.txt b/tests/metest-font-lock-test2-files/foo_expected.txt new file mode 100644 index 0000000000..c3e4b3c25c --- /dev/null +++ b/tests/metest-font-lock-test2-files/foo_expected.txt @@ -0,0 +1,3 @@ +kkkkkkkk v d fff + d d dd +kkk diff --git a/tests/metest-font-lock-test2.el b/tests/metest-font-lock-test2.el new file mode 100644 index 0000000000..7e4651ac80 --- /dev/null +++ b/tests/metest-font-lock-test2.el @@ -0,0 +1,131 @@ +;;; metest-font-lock-test2.el --- Testing suite for MATLAB Emacs -*- lexical-binding: t -*- +;; +;; Copyright 2025 Free Software Foundation, Inc. +;; +;; 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, 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 GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;; + +;;; Commentary: +;; +;; Tests to exercise font-lock using ./metest-font-lock-test2-files/* +;; + +;;; Code: + +(defun metest-font-lock-test2-files () + "Return list of full paths to each metest-font-lock-test2-files/*.m." + (directory-files "metest-font-lock-test2-files" t "\\.m$")) + +(defvar metest-font-lock-test2 (cons "font-lock-test2" (metest-font-lock-test2-files))) + +(defun metest-font-lock-test2 (&optional m-file) + "Test font-lock using ./metest-font-lock-test2-files/M-FILE. +Compare ./metest-font-lock-test2-files/M-FILE against +./metest-font-lock-test2-files/NAME_expected.txt, where NAME_expected.txt is of +same length as M-FILE and has a character for each face setup by +font-lock. + +If M-FILE is not provided, loop comparing all + ./metest-font-lock-test2-files/*.m + +For example, given foo.m containing + function a = foo + a = 1; + end +we'll have expected that looks like + kkkkkkkk v d fff + d d dd + kkk + +For debugging, you can run with a specified M-FILE, + M-: (metest-font-lock-test2 \"metest-font-lock-test2-files/M-FILE\")" + (let* ((m-files (if m-file + (progn + (setq m-file (file-truename m-file)) + (when (not (file-exists-p m-file)) + (error "File %s does not exist" m-file)) + (list m-file)) + (metest-font-lock-test2-files))) + (code-to-face '( + ("c" . font-lock-comment-face) + ("C" . font-lock-comment-delimiter-face) + ("d" . default) + ("f" . font-lock-function-name-face) + ("k" . font-lock-keyword-face) + ("t" . font-lock-type-face) + ("v" . font-lock-variable-name-face) + )) + (face-to-code (mapcar (lambda (pair) + (cons (cdr pair) (car pair))) + code-to-face))) + (dolist (m-file m-files) + (save-excursion + (message "START: metest-font-lock-test2 %s" m-file) + + (find-file m-file) + + ;; Force font lock to throw catchable errors. + (font-lock-mode 1) + (font-lock-flush (point-min) (point-max)) + (font-lock-ensure (point-min) (point-max)) + (font-lock-fontify-region (point-min) (point-max)) + + (goto-char (point-min)) + (let* ((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))))) + (while (not (eobp)) + (let* ((face (if (face-at-point) (face-at-point) 'default)) + (code (if (looking-at "\\([ \t]\\)") + (match-string 1) + (cdr (assoc face face-to-code))))) + (when (not code) + (error "Face, %S, is not in face-to-code alist" face)) + (setq got (concat got code)) + (forward-char) + (when (looking-at "\n") + (setq got (concat got "\n")) + (forward-char)))) + + (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)) + (when (= (length got) (length expected)) + (let* ((diff-idx (1- (compare-strings got nil nil expected nil nil))) + (got-code (substring got diff-idx (1+ diff-idx))) + (got-face (cdr (assoc got-code code-to-face))) + (expected-code (substring expected diff-idx (1+ diff-idx))) + (expected-face (cdr (assoc expected-code code-to-face)))) + (error "Baseline for %s does not match, got: %s, expected: %s. \ +Difference at column %d (got code-to-face \"%s\" . %S, expected code-to-face \"%s\" . %S" + m-file got-file expected-file + diff-idx + got-code got-face + expected-code expected-face))) + (error "Baseline for %s does not match, lengths are different, got: %s, expected: %s" + m-file got-file expected-file)) + (kill-buffer))) + (message "PASS: metest-font-lock-test2 %s" m-file))) + "success") + +(provide 'metest-font-lock-test2) +;;; metest-font-lock-test2.el ends here diff --git a/tests/metest.el b/tests/metest.el index 76f8f95511..5ce2e4a615 100644 --- a/tests/metest.el +++ b/tests/metest.el @@ -35,6 +35,9 @@ (require 'mlint) (require 'matlab-complete) +(add-to-list 'load-path ".") +(require 'metest-font-lock-test2) + (defun metest-all-syntax-tests () "Run all the syntax test cases in this file." (setq debug-on-error t) @@ -48,6 +51,7 @@ (metest-run 'metest-comment-string-syntax-test) (metest-run 'metest-fontlock-test) + (metest-run 'metest-font-lock-test2) (metest-run 'metest-sexp-counting-test) (metest-run 'metest-sexp-traversal-test)