branch: externals/matlab-mode
commit ae55b7528b70b1d0e848d29f0098a2cd02a298ce
Author: John Ciolfi <[email protected]>
Commit: John Ciolfi <[email protected]>

    matlab-ts-mode: handle incomplete strings in show-paren-mode matching
---
 contributing/treesit-mode-how-to.org               | 249 +++++++++++++++++++--
 matlab-ts-mode.el                                  |  15 +-
 .../show_paren_string.m                            |   6 +
 .../show_paren_string_expected.org                 |  50 +++++
 4 files changed, 304 insertions(+), 16 deletions(-)

diff --git a/contributing/treesit-mode-how-to.org 
b/contributing/treesit-mode-how-to.org
index 5420019e76..2413e326e2 100644
--- a/contributing/treesit-mode-how-to.org
+++ b/contributing/treesit-mode-how-to.org
@@ -227,7 +227,7 @@ You should now be able to use:
         (treesit-node-parent (treesit-node-at (point))))
   #+end_example
 
-* Font-lock
+* Setup: Font-lock
 
 TODO - add FIXME, XXX, and TODO marker coloring.
 
@@ -334,7 +334,7 @@ defaults to 3. If you'd like to have your font-lock default 
to level 4, add:
     )
 #+end_src
 
-** Font-lock Tests
+** Test: Font-lock
 
 It is recommended that you create tests to validate your font-lock set up and 
commit your tests with
 your code together. This will make it easier for you and others to update your 
code without causing
@@ -425,7 +425,7 @@ To run your tests in a build system, use
   ;;; test-language-ts-mode-font-lock.el ends here
 #+end_src
 
-* Indent
+* Setup: Indent
 
 Tree-sitter indentation is defined by =treesit-simple-indent-rules=.  We 
create a variable
 containing our N indent rules and tell tree-sitter about them. Notice that we 
create debug and
@@ -709,7 +709,7 @@ so we can combine them and also handle handle nested 
if-statements as shown belo
 Following this process, we complete our our indent engine by adding more 
rules. As we develop
 the rules, it is good to lockdown expected behavior with tests.
 
-** Indent Tests
+** Test: Indent
 
 We use a similar pattern for our indent tests:
 
@@ -772,7 +772,7 @@ where test-LANGUAGE-ts-mode-indent.el contains:
 
 #+end_src
 
-* Syntax Table
+* Setup: Syntax Table
 
 The Emacs "syntax table" is not related to the syntax tree created by 
tree-sitter. A syntax tree
 represents the hierarchical structure of your source code, giving a structural 
blueprint of your
@@ -966,7 +966,7 @@ This is good practice because these are fundamental to 
Emacs.
   ;;; LANGUAGE-ts-mode.el ends here
 #+end_src
 
-** Syntax Table Tests
+** Test: Syntax Table
 
 We follow a similar pattern for writing syntax table tests.
 
@@ -1010,7 +1010,7 @@ We follow a similar pattern for writing syntax table 
tests.
 
 #+end_src
 
-* treesit-thing-settings
+* Setup: treesit-thing-settings
 
 Examining treesit-major-mode-setup,
 
@@ -1033,7 +1033,7 @@ forward-sexp.
 'sentence is used by forward-sentance via forward-sentence-function which is 
set to
 treesit-forward-sentence.
 
-* Fill paragraph, M-q
+* Setup: Fill Paragraph, M-q
 
 =M-q= is bound to =prog-fill-reindent-defun= from =prog-mode=, which when the 
point is in a comment
 will fill the comment. If the point is in code it will indent the code. If the 
point is in a string,
@@ -1056,11 +1056,11 @@ e.g. if nodeName1 and nodeName2 should be filled like 
plain text, use:
 and in defun of LANGUAGE-ts-mode, add =(setq-local treesit-thing-settings
 LANGUAGE-ts-mode--thing-settings)= after you've setup your syntax table.
 
-** Fill paragraph tests
+** Test: Fill Paragraph
 
 TODO
 
-* treesit-defun-name-function
+* Setup: treesit-defun-name-function
 
 Emacs supports the concept of Change Logs for documentating changes.  With 
version control systems
 like git, there's less need for Change Logs, though the format of the Change 
Logs.  In Emacs using
@@ -1069,7 +1069,11 @@ defers to the =treesit-defun-name-function= to get 
information for the entry to
 
 TODO
 
-* IMenu
+** Test: treesit-defun-name-function
+
+TODO
+
+* Setup: IMenu
 
 Emacs =M-g i= (=M-x imenu=), makes it easy to jump to items in your file. If 
our mode populates
 imenu with the location of the function definitions, we can quickly jump to 
them by name. You can
@@ -1103,14 +1107,20 @@ tree-sitter parse tree and generate the index.
 
 TODO
 
-* treesit-outline-predicate
+** Test: IMenu
+
+TODO
+
+* Setup: Outline, treesit-outline-predicate
 
 This needs to be setup if treesit-simple-imenu-settings isn't set and you are 
using a custom
 imenu-create-index-function as we did above.
 
 TODO
 
-* Verify electric-pair-mode is good
+** Test: Outline
+
+* Setup: Electric Pair, electric-pair-mode
 
 =M-x electric-pair-mode= for most languages will just work. However, if your 
language
 uses typical characters that are paired, e.g. a single quote for a string 
delimiter and
@@ -1148,6 +1158,217 @@ also an operator such as a transpose, then you'll need 
to:
     )
 #+end_src
 
+** Test: Electric Pair.
+
+* Setup: show-paren-mode
+
+show-paren-mode uses =show-paren-data-function= to match "start" with "end" 
pairs. For example:
+
+ : myfcn(1, 2, 3)                  x = {1, 2, 3, 4}
+ :      ^       ^                      ^          ^
+ :    here     there                  here       there
+
+Your programming lanugage may have other items that should be paired. You can 
leverage
+show-paren-mode as a general "show pair mode". For example, you can extend 
show-paren-mode
+to show matching start/end quotes in a string:
+
+ : s = "foo bar"
+ :     ^       ^
+ :    here    there
+
+If your programming lanugage has block-like keywords, we can pair them. For 
example:
+
+ :  if condition
+ :  ^
+ :      myfcn(1, 2, 3)
+ :  end
+ :  ^
+
+To extend show-paren-mode, we set =show-paren-data-function= for our mode. 
Below we illustrate
+how to do string matching assuming strings can be created using ='single 
quotes'= or
+="double quotes"= where the string tree sitter nodes are:
+
+  : (string " (string_content) ")
+  : (string ' (string_content) ')
+
+#+begin_src emacs-lisp
+  (defun LANGUAGE-ts-mode--show-paren-or-block ()
+    "Function to assign to `show-paren-data-function'.
+  Highlight LANGUAGE pairs in addition to standard items paired by
+  `show-paren-mode'.  Returns a list: \\='(HERE-BEGIN HERE-END THERE-BEGIN
+  THERE-END MISMATCH) or nil."
+    (let* (here-begin
+           here-end
+           there-begin
+           there-end
+           mismatch
+           (pt (point))
+           (node (treesit-node-at pt)))
+
+      ;; If point is in whitespace, (treesit-node-at (point)) returns the 
nearest node. For
+      ;; paired matching we want the point on either a start or end paired 
item.
+      (let ((node-start (treesit-node-start node))
+            (node-end (treesit-node-end node)))
+        (when (and (>= pt node-start)
+                   (<= pt node-end))
+          (let* ((node-type (treesit-node-type node))
+                 (parent-node (treesit-node-parent node))
+                 (parent-type (treesit-node-type parent-node)))
+
+            (cond
+
+             ;; Case: on a single or double quote for a string.
+             ((and (or (equal "'" node-type)
+                       (equal "\"" node-type))
+                   (equal "string" parent-type))
+              (let (q-start-node
+                    q-end-node)
+                (if (= (treesit-node-start parent-node) (treesit-node-start 
node))
+                    ;; looking at start quote
+                    (setq q-start-node node
+                          q-end-node parent-node)
+                  ;; else looking at end quote
+                  (setq q-start-node parent-node
+                        q-end-node node))
+
+                (setq here-begin (treesit-node-start q-start-node))
+                (setq here-end (1+ here-begin))
+
+                (let* ((candidate-there-end (treesit-node-end q-end-node))
+                       (candidate-there-begin (1- candidate-there-end)))
+                  (cond
+                   ;; Case: Have starting quote of a string, but no content or 
closing quote.
+                   ((= here-begin candidate-there-begin)
+                    (setq mismatch t))
+                   ;; Case: Have starting quote, have string content, but no 
closing quote
+                   ((not (equal (char-after here-begin) (char-after 
candidate-there-begin)))
+                    (setq mismatch t))
+                   (t
+                    (setq there-begin candidate-there-begin)
+                    (setq there-end candidate-there-end))))))
+
+             ;; Add cases for other pairs.
+             ;; Note set mismatch to t if we have say a start keyword of a pair
+             ;; but are missing the end keyword.
+
+             ))))
+
+      (if (or here-begin here-end)
+          (list here-begin here-end there-begin there-end mismatch)
+       (funcall #'show-paren--default))))
+
+    (define-derived-mode language-ts-mode prog-mode "LANGUAGE:ts"
+      "Major mode for editing LANGUAGE files with tree-sitter."
+      ;; <snip>
+      (setq-local show-paren-data-function 
#'LANGUAGE-ts-mode--show-paren-or-block)
+    )
+#+end_src
+
+** Test: show-paren-mode
+
+Test file structure:
+
+ : LANGUAGE-ts-mode.el
+ : tests/test-LANUGAGE-ts-mode-show-paren.el
+ : tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM1.EXT
+ : tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM1_expected.org
+ : tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM2.EXT
+ : tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM2_expected.org
+ : ...
+
+where =tests/test-LANUGAGE-ts-mode-show-paren.el= contains:
+
+#+begin_src emacs-lisp
+  (require 't-utils)
+  (require 'LANGUAGE-ts-mode)
+
+  (cl-defun test-LANGUAGE-ts-mode-show-paren (&optional lang-file)
+    "Test defun movement using 
./test-LANGUAGE-ts-mode-show-paren-files/NAME.EXT.
+  Using ./test-LANGUAGE-ts-mode-show-paren-files/NAME.EXT, compare defun
+  movement against
+  ./test-LANGUAGE-ts-mode-show-paren-files/NAME_expected.org.  If LANG-FILE is
+  not provided, loop comparing all
+  ./test-LANGUAGE-ts-mode-show-paren-files/NAME.EXT files.
+
+  To add a test, create
+    ./test-LANGUAGE-ts-mode-show-paren-files/NAME.EXT
+  and run this function.  The baseline is saved for you as
+    ./test-LANGUAGE-ts-mode-show-paren-files/NAME_expected.org~
+  after validating it, rename it to
+    ./test-LANGUAGE-ts-mode-show-paren-files/NAME_expected.org"
+
+    (let ((test-name "test-LANGUAGE-ts-mode-show-paren"))
+
+      (when (not (t-utils-is-treesit-available 'LANGUAGE test-name))
+        (cl-return-from test-LANGUAGE-ts-mode-font-lock))
+
+      (let ((lang-files (t-utils-get-files (concat test-name "-files") 
"\\.EXT$" nil lang-file)))
+        (t-utils-test-xr test-name lang-files)))
+      "success")
+
+  (provide 'test-LANGUAGE-ts-mode-show-paren)
+#+end_src
+
+Each =tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM.EXT= file 
looks like the
+following assuming we have =% comment=" lines, replace with your language 
comments.
+
+#+begin_example
+
+  // -*- LANGUAGE-ts -*-
+
+    <snip - code to define string variables>
+
+% (t-utils-xr (re-search-forward "<") "C-b" "C-b" (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s1 = '<foo '' bar>';
+
+% (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s2 = '<foo '' bar>';
+
+% (t-utils-xr (re-search-forward "<") "C-b" "C-b" (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s3 = "<foo ' bar>";
+
+% (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s4 = "<foo ' bar>";
+
+% (t-utils-xr (re-search-forward "<") "C-b" "C-b" (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s5 = "<asdf
+
+% (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s6 = asdf>"
+    
+    <snip>
+
+#+end_example
+
+The tests are using the execute and record function, =t-utils-xr= which runs 
commands and records
+them into a =*.org= file. We run the test and if
+=tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM_expected.org= 
doesn't exist,
+=tests/test-LANUGAGE-ts-mode-show-paren-files/show_paren_ITEM_expected.org~= 
will be generated and
+after inspection rename the =*.org~= to =*.org=.
+
+For example, the last t-utils-xr result in the *.org file is below. Notice, 
that standard-output is
+"(910 911 nil nil t)" which indicates we have here-begin and here-end, but no 
there-begin and no
+there-end with mismatch true (t) because the string is missing the starting 
quote.
+
+#+begin_src org
+  ,* Executing commands from show_paren_string.lang:25:2:
+
+    (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
+
+  - Invoking      : (re-search-forward ">")
+    Start point   :  899
+    Moved to point:  910
+    : 26:10: s6 = asdf>"
+    :                  ^
+    No buffer modifications
+
+  - Invoking      : (prin1 (matlab-ts-mode--show-paren-or-block))
+    Start point   :  910
+    No point movement
+    standard-output:
+      (910 911 nil nil t)
+    No buffer modifications
+#+end_src
 
 * Final version
 
@@ -1732,7 +1953,7 @@ well worth writing a tree-sitter mode.
   3. There's no longer prompting if you want functions to have end's. This is 
now computed
      automatically.
 
-  4. Improved fill-paragraph, M-q, which will now fill comments and when not 
in a comment, indent
+  4. Improved fill-paragraph, =M-q=, which will now fill comments and when not 
in a comment, indent
      the current function or statement.
 
   5. Accurate type of m-file detection, which improves 
matlab-sections-minor-mode.
diff --git a/matlab-ts-mode.el b/matlab-ts-mode.el
index d77ce7794d..398858fc03 100644
--- a/matlab-ts-mode.el
+++ b/matlab-ts-mode.el
@@ -1362,8 +1362,19 @@ THERE-END MISMATCH) or nil."
 
               (setq here-begin (treesit-node-start q-start-node))
               (setq here-end (1+ here-begin))
-              (setq there-end (treesit-node-end q-end-node))
-              (setq there-begin (1- there-end))))
+
+              (let* ((candidate-there-end (treesit-node-end q-end-node))
+                     (candidate-there-begin (1- candidate-there-end)))
+                (cond
+                 ;; Case: Have starting quote of a string, but no content or 
closing quote.
+                 ((= here-begin candidate-there-begin)
+                  (setq mismatch t))
+                 ;; Case: Have starting quote, have string content, but no 
closing quote
+                 ((not (equal (char-after here-begin) (char-after 
candidate-there-begin)))
+                  (setq mismatch t))
+                 (t
+                  (setq there-begin candidate-there-begin)
+                  (setq there-end candidate-there-end))))))
 
            ))))
 
diff --git a/tests/test-matlab-ts-mode-show-paren-files/show_paren_string.m 
b/tests/test-matlab-ts-mode-show-paren-files/show_paren_string.m
index b58dedc738..e6e994fa84 100644
--- a/tests/test-matlab-ts-mode-show-paren-files/show_paren_string.m
+++ b/tests/test-matlab-ts-mode-show-paren-files/show_paren_string.m
@@ -18,3 +18,9 @@ s3 = "<foo ' bar>";
 
 % (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
 s4 = "<foo ' bar>";
+
+% (t-utils-xr (re-search-forward "<") "C-b" "C-b" (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s5 = "<asdf
+
+% (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
+s6 = asdf>"
diff --git 
a/tests/test-matlab-ts-mode-show-paren-files/show_paren_string_expected.org 
b/tests/test-matlab-ts-mode-show-paren-files/show_paren_string_expected.org
index 321e6f37fc..2f4561c21c 100644
--- a/tests/test-matlab-ts-mode-show-paren-files/show_paren_string_expected.org
+++ b/tests/test-matlab-ts-mode-show-paren-files/show_paren_string_expected.org
@@ -177,3 +177,53 @@
   standard-output:
     (689 690 701 702 nil)
   No buffer modifications
+
+* Executing commands from show_paren_string.m:22:2:
+
+  (t-utils-xr (re-search-forward "<") "C-b" "C-b" (prin1 
(matlab-ts-mode--show-paren-or-block)))
+
+- Invoking      : (re-search-forward "<")
+  Start point   :  801
+  Moved to point:  809
+  : 23:7: s5 = "<asdf
+  :              ^
+  No buffer modifications
+
+- Invoking      : "C-b" = backward-char
+  Start point   :  809
+  Moved to point:  808
+  : 23:6: s5 = "<asdf
+  :             ^
+  No buffer modifications
+
+- Invoking      : "C-b" = backward-char
+  Start point   :  808
+  Moved to point:  807
+  : 23:5: s5 = "<asdf
+  :            ^
+  No buffer modifications
+
+- Invoking      : (prin1 (matlab-ts-mode--show-paren-or-block))
+  Start point   :  807
+  No point movement
+  standard-output:
+    (807 808 nil nil t)
+  No buffer modifications
+
+* Executing commands from show_paren_string.m:25:2:
+
+  (t-utils-xr (re-search-forward ">") (prin1 
(matlab-ts-mode--show-paren-or-block)))
+
+- Invoking      : (re-search-forward ">")
+  Start point   :  899
+  Moved to point:  910
+  : 26:10: s6 = asdf>"
+  :                  ^
+  No buffer modifications
+
+- Invoking      : (prin1 (matlab-ts-mode--show-paren-or-block))
+  Start point   :  910
+  No point movement
+  standard-output:
+    (910 911 nil nil t)
+  No buffer modifications

Reply via email to