diff options
| author | Stefan Monnier | 2024-04-05 17:37:32 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2024-04-09 22:28:11 -0400 |
| commit | 820011a254231d6255b0d7fe07ff4ab1314c3b6e (patch) | |
| tree | ebee7d6bc1170be3a9ed50e6182c287d18292403 /lisp/progmodes | |
| parent | dd6b9c9426c5d7dde66974c5790815c3520a755b (diff) | |
| download | emacs-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.el | 64 |
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. |
| 1733 | LBP defaults to `eglot--bol'." | 1734 | LBP 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. |
| 1947 | Use `eglot-managed-p' to determine if current buffer is managed.") | 1969 | Use `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'. |
| 2590 | Records BEG, END and PRE-CHANGE-LENGTH locally." | 2622 | Records 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) |