aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/progmodes
diff options
context:
space:
mode:
authorStefan Monnier2024-04-05 17:37:32 -0400
committerStefan Monnier2024-04-09 22:28:11 -0400
commit820011a254231d6255b0d7fe07ff4ab1314c3b6e (patch)
treeebee7d6bc1170be3a9ed50e6182c287d18292403 /lisp/progmodes
parentdd6b9c9426c5d7dde66974c5790815c3520a755b (diff)
downloademacs-scratch/track-changes.tar.gz
emacs-scratch/track-changes.zip
lisp/emacs-lisp/track-changes.el: New filescratch/track-changes
This new package provides an API that is easier to use right than our `*-change-functions` hooks. The patch includes changes to `diff-mode.el` and `eglot.el` to make use of this new package. * lisp/emacs-lisp/track-changes.el: New file. * test/lisp/emacs-lisp/track-changes-tests.el: New file. * doc/lispref/text.texi (Tracking changes): New subsection. * lisp/progmodes/eglot.el: Require `track-changes`. (eglot--virtual-pos-to-lsp-position): New function. (eglot--track-changes): New var. (eglot--managed-mode): Use `track-changes-register` i.s.o `after/before-change-functions` when available. (eglot--track-changes-signal): New function, partly extracted from `eglot--after-change`. (eglot--after-change): Use it. (eglot--track-changes-fetch): New function. (eglot--signal-textDocument/didChange): Use it. * lisp/vc/diff-mode.el: Require `track-changes`. Also require `easy-mmode` before the `eval-when-compile`s. (diff-unhandled-changes): Delete variable. (diff-after-change-function): Delete function. (diff--track-changes-function): Rename from `diff-post-command-hook` and adjust to new calling convention. (diff--track-changes): New variable. (diff--track-changes-signal): New function. (diff-mode, diff-minor-mode): Use it with `track-changes-register`.
Diffstat (limited to 'lisp/progmodes')
-rw-r--r--lisp/progmodes/eglot.el64
1 files changed, 60 insertions, 4 deletions
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 7f4284bf09d..478e7687bb3 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -110,6 +110,7 @@
110(require 'text-property-search nil t) 110(require 'text-property-search nil t)
111(require 'diff-mode) 111(require 'diff-mode)
112(require 'diff) 112(require 'diff)
113(require 'track-changes nil t)
113 114
114;; These dependencies are also GNU ELPA core packages. Because of 115;; These dependencies are also GNU ELPA core packages. Because of
115;; bug#62576, since there is a risk that M-x package-install, despite 116;; bug#62576, since there is a risk that M-x package-install, despite
@@ -1732,6 +1733,9 @@ return value is fed through the corresponding inverse function
1732 "Calculate number of UTF-16 code units from position given by LBP. 1733 "Calculate number of UTF-16 code units from position given by LBP.
1733LBP defaults to `eglot--bol'." 1734LBP defaults to `eglot--bol'."
1734 (/ (- (length (encode-coding-region (or lbp (eglot--bol)) 1735 (/ (- (length (encode-coding-region (or lbp (eglot--bol))
1736 ;; FIXME: How could `point' ever be
1737 ;; larger than `point-max' (sounds like
1738 ;; a bug in Emacs).
1735 ;; Fix github#860 1739 ;; Fix github#860
1736 (min (point) (point-max)) 'utf-16 t)) 1740 (min (point) (point-max)) 'utf-16 t))
1737 2) 1741 2)
@@ -1749,6 +1753,24 @@ LBP defaults to `eglot--bol'."
1749 :character (progn (when pos (goto-char pos)) 1753 :character (progn (when pos (goto-char pos))
1750 (funcall eglot-current-linepos-function))))) 1754 (funcall eglot-current-linepos-function)))))
1751 1755
1756(defun eglot--virtual-pos-to-lsp-position (pos string)
1757 "Return the LSP position at the end of STRING if it were inserted at POS."
1758 (eglot--widening
1759 (goto-char pos)
1760 (forward-line 0)
1761 ;; LSP line is zero-origin; Emacs is one-origin.
1762 (let ((posline (1- (line-number-at-pos nil t)))
1763 (linebeg (buffer-substring (point) pos))
1764 (colfun eglot-current-linepos-function))
1765 ;; Use a temp buffer because:
1766 ;; - I don't know of a fast way to count newlines in a string.
1767 ;; - We currently don't have `eglot-current-linepos-function' for strings.
1768 (with-temp-buffer
1769 (insert linebeg string)
1770 (goto-char (point-max))
1771 (list :line (+ posline (1- (line-number-at-pos nil t)))
1772 :character (funcall colfun))))))
1773
1752(defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos 1774(defvar eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos
1753 "Function to move to a position within a line reported by the LSP server. 1775 "Function to move to a position within a line reported by the LSP server.
1754 1776
@@ -1946,6 +1968,8 @@ For example, to keep your Company customization, add the symbol
1946 "A hook run by Eglot after it started/stopped managing a buffer. 1968 "A hook run by Eglot after it started/stopped managing a buffer.
1947Use `eglot-managed-p' to determine if current buffer is managed.") 1969Use `eglot-managed-p' to determine if current buffer is managed.")
1948 1970
1971(defvar-local eglot--track-changes nil)
1972
1949(define-minor-mode eglot--managed-mode 1973(define-minor-mode eglot--managed-mode
1950 "Mode for source buffers managed by some Eglot project." 1974 "Mode for source buffers managed by some Eglot project."
1951 :init-value nil :lighter nil :keymap eglot-mode-map 1975 :init-value nil :lighter nil :keymap eglot-mode-map
@@ -1959,8 +1983,13 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
1959 ("utf-8" 1983 ("utf-8"
1960 (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-linepos) 1984 (eglot--setq-saving eglot-current-linepos-function #'eglot-utf-8-linepos)
1961 (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-utf-8-linepos))) 1985 (eglot--setq-saving eglot-move-to-linepos-function #'eglot-move-to-utf-8-linepos)))
1962 (add-hook 'after-change-functions #'eglot--after-change nil t) 1986 (if (fboundp 'track-changes-register)
1963 (add-hook 'before-change-functions #'eglot--before-change nil t) 1987 (unless eglot--track-changes
1988 (setq eglot--track-changes
1989 (track-changes-register
1990 #'eglot--track-changes-signal :disjoint t)))
1991 (add-hook 'after-change-functions #'eglot--after-change nil t)
1992 (add-hook 'before-change-functions #'eglot--before-change nil t))
1964 (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) 1993 (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t)
1965 ;; Prepend "didClose" to the hook after the "nonoff", so it will run first 1994 ;; Prepend "didClose" to the hook after the "nonoff", so it will run first
1966 (add-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose nil t) 1995 (add-hook 'kill-buffer-hook #'eglot--signal-textDocument/didClose nil t)
@@ -1998,6 +2027,9 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
1998 buffer 2027 buffer
1999 (eglot--managed-buffers (eglot-current-server))))) 2028 (eglot--managed-buffers (eglot-current-server)))))
2000 (t 2029 (t
2030 (when eglot--track-changes
2031 (track-changes-unregister eglot--track-changes)
2032 (setq eglot--track-changes nil))
2001 (remove-hook 'after-change-functions #'eglot--after-change t) 2033 (remove-hook 'after-change-functions #'eglot--after-change t)
2002 (remove-hook 'before-change-functions #'eglot--before-change t) 2034 (remove-hook 'before-change-functions #'eglot--before-change t)
2003 (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) 2035 (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t)
@@ -2588,7 +2620,6 @@ buffer."
2588(defun eglot--after-change (beg end pre-change-length) 2620(defun eglot--after-change (beg end pre-change-length)
2589 "Hook onto `after-change-functions'. 2621 "Hook onto `after-change-functions'.
2590Records BEG, END and PRE-CHANGE-LENGTH locally." 2622Records BEG, END and PRE-CHANGE-LENGTH locally."
2591 (cl-incf eglot--versioned-identifier)
2592 (pcase (car-safe eglot--recent-changes) 2623 (pcase (car-safe eglot--recent-changes)
2593 (`(,lsp-beg ,lsp-end 2624 (`(,lsp-beg ,lsp-end
2594 (,b-beg . ,b-beg-marker) 2625 (,b-beg . ,b-beg-marker)
@@ -2616,6 +2647,29 @@ Records BEG, END and PRE-CHANGE-LENGTH locally."
2616 `(,lsp-beg ,lsp-end ,pre-change-length 2647 `(,lsp-beg ,lsp-end ,pre-change-length
2617 ,(buffer-substring-no-properties beg end))))) 2648 ,(buffer-substring-no-properties beg end)))))
2618 (_ (setf eglot--recent-changes :emacs-messup))) 2649 (_ (setf eglot--recent-changes :emacs-messup)))
2650 (eglot--track-changes-signal nil))
2651
2652(defun eglot--track-changes-fetch (id)
2653 (if (eq eglot--recent-changes :pending) (setq eglot--recent-changes nil))
2654 (track-changes-fetch
2655 id (lambda (beg end before)
2656 (cond
2657 ((eq eglot--recent-changes :emacs-messup) nil)
2658 ((eq before 'error) (setf eglot--recent-changes :emacs-messup))
2659 (t (push `(,(eglot--pos-to-lsp-position beg)
2660 ,(eglot--virtual-pos-to-lsp-position beg before)
2661 ,(length before)
2662 ,(buffer-substring-no-properties beg end))
2663 eglot--recent-changes))))))
2664
2665(defun eglot--track-changes-signal (id &optional distance)
2666 (cl-incf eglot--versioned-identifier)
2667 (cond
2668 (distance (eglot--track-changes-fetch id))
2669 (eglot--recent-changes nil)
2670 ;; Note that there are pending changes, for the benefit of those
2671 ;; who check it as a boolean.
2672 (t (setq eglot--recent-changes :pending)))
2619 (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) 2673 (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer))
2620 (let ((buf (current-buffer))) 2674 (let ((buf (current-buffer)))
2621 (setq eglot--change-idle-timer 2675 (setq eglot--change-idle-timer
@@ -2729,6 +2783,8 @@ When called interactively, use the currently active server"
2729(defun eglot--signal-textDocument/didChange () 2783(defun eglot--signal-textDocument/didChange ()
2730 "Send textDocument/didChange to server." 2784 "Send textDocument/didChange to server."
2731 (when eglot--recent-changes 2785 (when eglot--recent-changes
2786 (when eglot--track-changes
2787 (eglot--track-changes-fetch eglot--track-changes))
2732 (let* ((server (eglot--current-server-or-lose)) 2788 (let* ((server (eglot--current-server-or-lose))
2733 (sync-capability (eglot-server-capable :textDocumentSync)) 2789 (sync-capability (eglot-server-capable :textDocumentSync))
2734 (sync-kind (if (numberp sync-capability) sync-capability 2790 (sync-kind (if (numberp sync-capability) sync-capability
@@ -2750,7 +2806,7 @@ When called interactively, use the currently active server"
2750 ;; empty entries in `eglot--before-change' calls 2806 ;; empty entries in `eglot--before-change' calls
2751 ;; without an `eglot--after-change' reciprocal. 2807 ;; without an `eglot--after-change' reciprocal.
2752 ;; Weed them out here. 2808 ;; Weed them out here.
2753 when (numberp len) 2809 when (numberp len) ;FIXME: Not needed with `track-changes'.
2754 vconcat `[,(list :range `(:start ,beg :end ,end) 2810 vconcat `[,(list :range `(:start ,beg :end ,end)
2755 :rangeLength len :text text)])))) 2811 :rangeLength len :text text)]))))
2756 (setq eglot--recent-changes nil) 2812 (setq eglot--recent-changes nil)