From 9d0740678edd296f46429d3d50f103050b81be74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Tue, 20 Jun 2023 14:51:27 +0200 Subject: [PATCH 1/8] WIP: option to restore previous TAB behavior --- julia-mode.el | 69 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/julia-mode.el b/julia-mode.el index 7f8f7c1..235675e 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -68,20 +68,28 @@ unicode for LaTeX even if disabled." :type 'boolean) (defconst julia-mode--latexsubs-partials - (let ((table (make-hash-table :test 'equal))) - (maphash (lambda (latex _subst) - (cl-assert (string= (substring latex 0 1) "\\") nil - "LaTeX substitution does not start with \\.") - (let ((len (length latex))) - (cl-assert (< 1 len) nil "Trivially short LaTeX subtitution") - ;; for \foo, put f, fo, foo into the table - (cl-loop for i from 2 to len - do (puthash (substring latex 1 i) t table)))) - julia-mode-latexsubs) - table) + (let ((table-unordered (make-hash-table :test 'equal)) + (table-ordered (make-hash-table :test 'equal))) + (cl-flet ((_append (key replacement) + (puthash key (cons replacement (gethash key table-unordered nil)) table-unordered))) + ;; accumulate partials + (maphash (lambda (latex replacement) + (cl-assert (string= (substring latex 0 1) "\\") nil + "LaTeX substitution does not start with \\.") + (let ((len (length latex))) + (cl-assert (< 1 len) nil "Trivially short LaTeX subtitution") + ;; for \foo, put f, fo, foo into the table + (cl-loop for i from 2 to len + do (_append (substring latex 1 i) (cons latex replacement))))) + julia-mode-latexsubs) + ;; order by LaTeX part + (maphash (lambda (partial replacements) + (puthash partial (sort replacements (lambda (a b) (string< (car a) (car b)))) table-ordered)) + table-unordered)) + table-ordered) "A hash table containing all partial strings from the LaTeX abbreviations in -`julia-mode-latexsubs' as keys. Values are always `t', the purpose is to -represent a set.") +`julia-mode-latexsubs' as keys. Values are lists of the `(cons latex replacement)`, +ordered by the `latex` part.") (defun julia-mode--latexsubs-longest-partial-end (beg) "Starting at `beg' (should be the \"\\\"), return the end of the longest @@ -92,10 +100,10 @@ partial match for LaTeX completion, or `nil' when not applicable." (forward-char) (let ((beg (point))) (cl-flet ((next-char-matches? () - (let* ((end (1+ (point))) - (str (buffer-substring-no-properties beg end)) - (valid? (gethash str julia-mode--latexsubs-partials))) - valid?))) + (let* ((end (1+ (point))) + (str (buffer-substring-no-properties beg end)) + (valid? (gethash str julia-mode--latexsubs-partials))) + valid?))) (while (and (not (eobp)) (next-char-matches?)) (forward-char))) (point))))) @@ -908,6 +916,33 @@ buffer where the LaTeX symbol starts." (abbrev-insert symb name beg end))) #'ignore)) +(defun julia-mode--latexsub-before-point () + "When there is a LaTeX substitution that can be made before the point, return (CONS BEG SUBSITUTION). + +`beg' is the position of the `\`, `substitution' is the replacement. When multiple options match, ask the user to clarify via `completing-read'" + (when-let (beg (julia--latexsub-start-symbol)) + (let ((partial (buffer-substring-no-properties (1+ beg) (point)))) + (when-let (replacements (gethash partial julia-mode--latexsubs-partials)) + (cons beg + (if (cdr replacements) + (gethash (completing-read "LaTeX completions: " (mapcar #'car replacements)) julia-mode-latexsubs) + (cdar replacements))))))) + +(defun julia-latexsub-or-indent (arg) + "Either indent according to Julia mode conventions or perform a LaTeX-like symbol substution. + +Presently, this is not the default. Enable with eg + +(define-key julia-mode-map (kbd \"TAB\") \'julia-latexsub-or-indent) + + in your `julia-mode-hook'." + (interactive "*i") + (if-let (replacement (julia-mode--latexsub-before-point)) + (progn + (delete-backward-char (- (point) (car replacement))) + (insert (cdr replacement))) + (julia-indent-line))) + ;; Math insertion in julia. Use it with ;; (add-hook 'julia-mode-hook 'julia-math-mode) From 1c2c9d494c496eb4d04caa189c6bd50ada501ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Tue, 20 Jun 2023 15:35:56 +0200 Subject: [PATCH 2/8] don't need quote in docstring? --- julia-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia-mode.el b/julia-mode.el index 235675e..c1e7150 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -933,7 +933,7 @@ buffer where the LaTeX symbol starts." Presently, this is not the default. Enable with eg -(define-key julia-mode-map (kbd \"TAB\") \'julia-latexsub-or-indent) +(define-key julia-mode-map (kbd \"TAB\") 'julia-latexsub-or-indent) in your `julia-mode-hook'." (interactive "*i") From fef78c5ae39a7ca4bb265df6de48396650f910ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Tue, 20 Jun 2023 16:06:41 +0200 Subject: [PATCH 3/8] Add greedy match option. Simplify code by saving the `\` and looking up the match from the primary hash table. --- julia-mode.el | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/julia-mode.el b/julia-mode.el index c1e7150..5b74f14 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -67,29 +67,32 @@ User can still use `abbrev-mode' or `expand-abbrev' to substitute unicode for LaTeX even if disabled." :type 'boolean) +(defcustom julia-latexsub-greedy t + "When `t', `julia-latexsub-or-indent' does not offer options when a complete match is found. Eg for \"\\bar\", \"\\barcap\" etc will not be offered in a prompt." + :type 'boolean) + (defconst julia-mode--latexsubs-partials (let ((table-unordered (make-hash-table :test 'equal)) (table-ordered (make-hash-table :test 'equal))) (cl-flet ((_append (key replacement) (puthash key (cons replacement (gethash key table-unordered nil)) table-unordered))) ;; accumulate partials - (maphash (lambda (latex replacement) + (maphash (lambda (latex _unicode) (cl-assert (string= (substring latex 0 1) "\\") nil "LaTeX substitution does not start with \\.") (let ((len (length latex))) (cl-assert (< 1 len) nil "Trivially short LaTeX subtitution") - ;; for \foo, put f, fo, foo into the table + ;; for \foo, put \f, \fo, \foo into the table (cl-loop for i from 2 to len - do (_append (substring latex 1 i) (cons latex replacement))))) + do (_append (substring latex 0 i) latex)))) julia-mode-latexsubs) ;; order by LaTeX part (maphash (lambda (partial replacements) - (puthash partial (sort replacements (lambda (a b) (string< (car a) (car b)))) table-ordered)) + (puthash partial (sort replacements #'string<) table-ordered)) table-unordered)) table-ordered) "A hash table containing all partial strings from the LaTeX abbreviations in -`julia-mode-latexsubs' as keys. Values are lists of the `(cons latex replacement)`, -ordered by the `latex` part.") +`julia-mode-latexsubs' as keys. Values are sorted lists of complete \"\\some_string\".") (defun julia-mode--latexsubs-longest-partial-end (beg) "Starting at `beg' (should be the \"\\\"), return the end of the longest @@ -917,20 +920,27 @@ buffer where the LaTeX symbol starts." #'ignore)) (defun julia-mode--latexsub-before-point () - "When there is a LaTeX substitution that can be made before the point, return (CONS BEG SUBSITUTION). + "When there is a LaTeX substitution that can be made before the point, return (CONS BEG LATEX). -`beg' is the position of the `\`, `substitution' is the replacement. When multiple options match, ask the user to clarify via `completing-read'" +`beg' is the position of the `\`, `latex' is the string to replace, including the `\`. + +When multiple options match, ask the user to clarify via `completing-read', unless there is a complete match and `julia-latexsub-greedy' is `t'." (when-let (beg (julia--latexsub-start-symbol)) - (let ((partial (buffer-substring-no-properties (1+ beg) (point)))) + (let ((partial (buffer-substring-no-properties beg (point)))) + (message "partial %s" partial) (when-let (replacements (gethash partial julia-mode--latexsubs-partials)) - (cons beg - (if (cdr replacements) - (gethash (completing-read "LaTeX completions: " (mapcar #'car replacements)) julia-mode-latexsubs) - (cdar replacements))))))) + (message "replacements %s" (prin1 replacements)) + (let* ((complete-match (member partial replacements)) + (replacement (cond ((and complete-match julia-latexsub-greedy) partial) + ((cdr replacements) (gethash (completing-read "LaTeX completions: " replacements) julia-mode-latexsubs)) + (t (car replacements))))) + (cons beg replacement)))))) (defun julia-latexsub-or-indent (arg) "Either indent according to Julia mode conventions or perform a LaTeX-like symbol substution. +When multiple options match, ask the user to clarify via `completing-read', unless there is a complete match and `julia-latexsub-greedy' is `t'. + Presently, this is not the default. Enable with eg (define-key julia-mode-map (kbd \"TAB\") 'julia-latexsub-or-indent) @@ -940,7 +950,7 @@ Presently, this is not the default. Enable with eg (if-let (replacement (julia-mode--latexsub-before-point)) (progn (delete-backward-char (- (point) (car replacement))) - (insert (cdr replacement))) + (insert (gethash (cdr replacement) julia-mode-latexsubs))) (julia-indent-line))) ;; Math insertion in julia. Use it with From b390203f0cf2e0004c3a505684d9a01cde620d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Tue, 20 Jun 2023 16:32:36 +0200 Subject: [PATCH 4/8] oops, now we include \\, this should fix tests --- julia-mode.el | 1 - 1 file changed, 1 deletion(-) diff --git a/julia-mode.el b/julia-mode.el index 5b74f14..c8fcac6 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -100,7 +100,6 @@ partial match for LaTeX completion, or `nil' when not applicable." (save-excursion (goto-char beg) (when (and (= (char-after) ?\\) (not (eobp))) - (forward-char) (let ((beg (point))) (cl-flet ((next-char-matches? () (let* ((end (1+ (point))) From 7b00566570ae40eb16b40fe9bf5f05835fd64c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Tue, 20 Jun 2023 16:38:49 +0200 Subject: [PATCH 5/8] fix --- julia-mode.el | 1 + 1 file changed, 1 insertion(+) diff --git a/julia-mode.el b/julia-mode.el index c8fcac6..e18b9ab 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -101,6 +101,7 @@ partial match for LaTeX completion, or `nil' when not applicable." (goto-char beg) (when (and (= (char-after) ?\\) (not (eobp))) (let ((beg (point))) + (forward-char) ; move past the \ (cl-flet ((next-char-matches? () (let* ((end (1+ (point))) (str (buffer-substring-no-properties beg end)) From 86da879ec2b7e7d252426cf61148ef37ceb7307a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Wed, 10 Apr 2024 16:09:57 +0200 Subject: [PATCH 6/8] fixes suggested by @dhanak (thanks!) --- julia-mode.el | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/julia-mode.el b/julia-mode.el index a5ba984..bac21b1 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -938,12 +938,10 @@ buffer where the LaTeX symbol starts." When multiple options match, ask the user to clarify via `completing-read', unless there is a complete match and `julia-latexsub-greedy' is `t'." (when-let (beg (julia--latexsub-start-symbol)) (let ((partial (buffer-substring-no-properties beg (point)))) - (message "partial %s" partial) (when-let (replacements (gethash partial julia-mode--latexsubs-partials)) - (message "replacements %s" (prin1 replacements)) (let* ((complete-match (member partial replacements)) (replacement (cond ((and complete-match julia-latexsub-greedy) partial) - ((cdr replacements) (gethash (completing-read "LaTeX completions: " replacements) julia-mode-latexsubs)) + ((cdr replacements) (completing-read "LaTeX completions: " replacements)) (t (car replacements))))) (cons beg replacement)))))) From 5f2e774535908a93e9649bf6531332acfd863033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Wed, 10 Apr 2024 16:40:40 +0200 Subject: [PATCH 7/8] Make selection customizable. --- julia-mode.el | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/julia-mode.el b/julia-mode.el index bac21b1..877b667 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -71,11 +71,22 @@ unicode for LaTeX even if disabled." "When `t', `julia-latexsub-or-indent' does not offer options when a complete match is found. Eg for \"\\bar\", \"\\barcap\" etc will not be offered in a prompt." :type 'boolean) +(defun julia-latexsub-selector-completing-read (replacements) + "Use `completing-read' to pick an item from REPLACEMENTS." + (completing-read "LaTeX completions: " replacements (lambda (&rest _) t) t)) + +(defvar julia-latexsub-selector 'julia-latexsub-selector-completing-read + "A function that is called when the `julia-latexsub-or-indent' finds multiple matches for a prefix. + +The argument is a list of strings. The function should ALWAYS return an item from this list, otherwise an error occurs. + +The default implementation uses `completing-read'.") + (defconst julia-mode--latexsubs-partials (let ((table-unordered (make-hash-table :test 'equal)) (table-ordered (make-hash-table :test 'equal))) (cl-flet ((_append (key replacement) - (puthash key (cons replacement (gethash key table-unordered nil)) table-unordered))) + (puthash key (cons replacement (gethash key table-unordered nil)) table-unordered))) ;; accumulate partials (maphash (lambda (latex _unicode) (cl-assert (string= (substring latex 0 1) "\\") nil @@ -935,20 +946,24 @@ buffer where the LaTeX symbol starts." `beg' is the position of the `\`, `latex' is the string to replace, including the `\`. -When multiple options match, ask the user to clarify via `completing-read', unless there is a complete match and `julia-latexsub-greedy' is `t'." +When multiple options match, ask the user to clarify via `julia-latexsub-selector', unless there is a complete match and `julia-latexsub-greedy' is `t'." (when-let (beg (julia--latexsub-start-symbol)) (let ((partial (buffer-substring-no-properties beg (point)))) (when-let (replacements (gethash partial julia-mode--latexsubs-partials)) (let* ((complete-match (member partial replacements)) - (replacement (cond ((and complete-match julia-latexsub-greedy) partial) - ((cdr replacements) (completing-read "LaTeX completions: " replacements)) - (t (car replacements))))) + (replacement (cond + ;; complete match w/ greedy + ((and complete-match julia-latexsub-greedy) partial) + ;; multiple replacements, ask user + ((cdr replacements) (funcall julia-latexsub-selector replacements)) + ;; single replacement, pick that + (t (car replacements))))) (cons beg replacement)))))) -(defun julia-latexsub-or-indent (arg) +(defun julia-latexsub-or-indent (arg) "Either indent according to Julia mode conventions or perform a LaTeX-like symbol substution. -When multiple options match, ask the user to clarify via `completing-read', unless there is a complete match and `julia-latexsub-greedy' is `t'. +When multiple options match, ask the user to clarify via `julia-latexsub-selector', unless there is a complete match and `julia-latexsub-greedy' is `t'. Presently, this is not the default. Enable with eg From 9bcca66b2116dce715f4ae712edb4494506eb8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20K=2E=20Papp?= Date: Wed, 10 Apr 2024 17:57:27 +0200 Subject: [PATCH 8/8] add tests, minor corrections --- julia-mode-tests.el | 43 +++++++++++++++++++++++++++++++++++++++++++ julia-mode.el | 22 +++++++++++----------- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/julia-mode-tests.el b/julia-mode-tests.el index 45db3c7..b42237d 100644 --- a/julia-mode-tests.el +++ b/julia-mode-tests.el @@ -1009,6 +1009,49 @@ hello world (string-to-syntax "\\") (syntax-after 13))))) +;;; testing julia-latexsub-or-indent + +(cl-defun julia-test-latexsub-or-indent (from &key (position (1+ (length from))) (greedy t)) + "Utility function to test `julia-latexsub-or-indent'. + +This is how it works: + +1. FROM is inserted in a buffer. + +2. The point is moved to POSITION. + +3. `julia-latexsub-or-indent' is called on the buffer. + +If `julia-latexsub-selector' is called, it selects the first replacement, which is also placed in SELECTION (otherwise it is NIL). + +Return a cons of the + +1. buffer contents + +2. the replacement of SELECTION when not nil. + +The latter can be used to construct test comparisons." + (let* ((selection) + (julia-latexsub-selector + (lambda (replacements) + (setf selection (car replacements)) + selection)) + (julia-latexsub-greedy greedy)) + (cons (with-temp-buffer + (insert from) + (goto-char position) + (julia-latexsub-or-indent t) + (buffer-string)) + (gethash selection julia-mode-latexsubs)))) + +(ert-deftest julia--test-latexsub-or-indent () + (should (equal (julia-test-latexsub-or-indent "\\circ") '("∘"))) + (let ((result (julia-test-latexsub-or-indent "\\circXX" :position 5))) + (should (equal (car result) (concat (cdr result) "cXX")))) + (let ((result (julia-test-latexsub-or-indent "\\circ" :greedy nil))) + (should (equal (car result) (cdr result)))) + (should (equal (julia-test-latexsub-or-indent "\\alpha") '("α")))) + ;;; ;;; run all tests ;;; diff --git a/julia-mode.el b/julia-mode.el index 877b667..e877737 100644 --- a/julia-mode.el +++ b/julia-mode.el @@ -951,16 +951,16 @@ When multiple options match, ask the user to clarify via `julia-latexsub-selecto (let ((partial (buffer-substring-no-properties beg (point)))) (when-let (replacements (gethash partial julia-mode--latexsubs-partials)) (let* ((complete-match (member partial replacements)) - (replacement (cond - ;; complete match w/ greedy - ((and complete-match julia-latexsub-greedy) partial) - ;; multiple replacements, ask user - ((cdr replacements) (funcall julia-latexsub-selector replacements)) - ;; single replacement, pick that - (t (car replacements))))) - (cons beg replacement)))))) - -(defun julia-latexsub-or-indent (arg) + (latex (cond + ;; complete match w/ greedy + ((and complete-match julia-latexsub-greedy) partial) + ;; multiple replacements, ask user + ((cdr replacements) (funcall julia-latexsub-selector replacements)) + ;; single replacement, pick that + (t (car replacements))))) + (cons beg latex)))))) + +(defun julia-latexsub-or-indent (arg) "Either indent according to Julia mode conventions or perform a LaTeX-like symbol substution. When multiple options match, ask the user to clarify via `julia-latexsub-selector', unless there is a complete match and `julia-latexsub-greedy' is `t'. @@ -969,7 +969,7 @@ Presently, this is not the default. Enable with eg (define-key julia-mode-map (kbd \"TAB\") 'julia-latexsub-or-indent) - in your `julia-mode-hook'." +eg in your `julia-mode-hook'." (interactive "*i") (if-let (replacement (julia-mode--latexsub-before-point)) (progn