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)
 

Reply via email to