aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/lsp-mode-20220505.630/lsp-lens.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/lsp-mode-20220505.630/lsp-lens.el')
-rw-r--r--elpa/lsp-mode-20220505.630/lsp-lens.el448
1 files changed, 448 insertions, 0 deletions
diff --git a/elpa/lsp-mode-20220505.630/lsp-lens.el b/elpa/lsp-mode-20220505.630/lsp-lens.el
new file mode 100644
index 0000000..5cecff4
--- /dev/null
+++ b/elpa/lsp-mode-20220505.630/lsp-lens.el
@@ -0,0 +1,448 @@
+;;; lsp-lens.el --- LSP lens -*- lexical-binding: t; -*-
+;;
+;; Copyright (C) 2020 emacs-lsp maintainers
+;;
+;; 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 of the License, 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 this program. If not, see <https://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+;;
+;; LSP lens
+;;
+;;; Code:
+
+(require 'lsp-mode)
+
+(defgroup lsp-lens nil
+ "LSP support for lens"
+ :prefix "lsp-lens-"
+ :group 'lsp-mode
+ :tag "LSP Lens")
+
+(defcustom lsp-lens-debounce-interval 0.001
+ "Debounce interval for loading lenses."
+ :group 'lsp-lens
+ :type 'number)
+
+(defcustom lsp-lens-place-position 'end-of-line
+ "The position to place lens relative to returned lens position."
+ :group 'lsp-lens
+ :type '(choice (const above-line)
+ (const end-of-line))
+ :package-version '(lsp-mode . "8.0.0"))
+
+(defface lsp-lens-mouse-face
+ '((t :height 0.8 :inherit link))
+ "The face used for code lens overlays."
+ :group 'lsp-lens)
+
+(defface lsp-lens-face
+ '((t :inherit lsp-details-face))
+ "The face used for code lens overlays."
+ :group 'lsp-lens)
+
+(defvar-local lsp-lens--modified? nil)
+
+(defvar-local lsp-lens--overlays nil
+ "Current lenses.")
+
+(defvar-local lsp-lens--page nil
+ "Pair of points which holds the last window location the lenses were loaded.")
+
+(defvar-local lsp-lens--last-count nil
+ "The number of lenses the last time they were rendered.")
+
+(defvar lsp-lens-backends '(lsp-lens--backend)
+ "Backends providing lenses.")
+
+(defvar-local lsp-lens--refresh-timer nil
+ "Refresh timer for the lenses.")
+
+(defvar-local lsp-lens--data nil
+ "Pair of points which holds the last window location the lenses were loaded.")
+
+(defvar-local lsp-lens--backend-cache nil)
+
+(defun lsp-lens--text-width (from to)
+ "Measure the width of the text between FROM and TO.
+Results are meaningful only if FROM and TO are on the same line."
+ ;; `current-column' takes prettification into account
+ (- (save-excursion (goto-char to) (current-column))
+ (save-excursion (goto-char from) (current-column))))
+
+(defun lsp-lens--update (ov)
+ "Redraw quick-peek overlay OV."
+ (let* ((offset (lsp-lens--text-width (save-excursion
+ (beginning-of-visual-line)
+ (point))
+ (save-excursion
+ (beginning-of-line-text)
+ (point))))
+ (str (if (eq 'end-of-line lsp-lens-place-position)
+ (overlay-get ov 'lsp--lens-contents)
+ (concat (make-string offset ?\s)
+ (overlay-get ov 'lsp--lens-contents)))))
+ (save-excursion
+ (goto-char (overlay-start ov))
+ (if (eq 'end-of-line lsp-lens-place-position)
+ (overlay-put ov 'after-string (propertize (concat " " str) 'cursor t))
+ (overlay-put ov 'before-string (concat str "\n")))
+ (overlay-put ov 'lsp-original str))))
+
+(defun lsp-lens--overlay-ensure-at (pos)
+ "Find or create a lens for the line at POS."
+ (-doto (save-excursion
+ (goto-char pos)
+ (if (eq 'end-of-line lsp-lens-place-position)
+ (make-overlay (point-at-eol) -1 nil t t)
+ (make-overlay (point-at-bol) (1+ (point-at-eol)) nil t t)))
+ (overlay-put 'lsp-lens t)
+ (overlay-put 'evaporate t)
+ (overlay-put 'lsp-lens-position pos)))
+
+(defun lsp-lens--show (str pos metadata)
+ "Show STR in an inline window at POS including METADATA."
+ (let ((ov (lsp-lens--overlay-ensure-at pos)))
+ (save-excursion
+ (goto-char pos)
+ (setf (overlay-get ov 'lsp--lens-contents) str)
+ (setf (overlay-get ov 'lsp--metadata) metadata)
+ (lsp-lens--update ov)
+ ov)))
+
+(defun lsp-lens--idle-function (&optional buffer)
+ "Create idle function for buffer BUFFER."
+ (when (and (or (not buffer) (eq (current-buffer) buffer))
+ (not (equal (cons (window-start) (window-end)) lsp-lens--page)))
+ (lsp-lens--schedule-refresh nil)))
+
+(defun lsp-lens--overlay-matches-pos (ov pos)
+ "Check if OV is a lens covering POS."
+ (and (overlay-get ov 'lsp-lens)
+ (overlay-start ov)
+ (<= (overlay-start ov) pos)
+ (< pos (overlay-end ov))))
+
+(defun lsp-lens--after-save ()
+ "Handler for `after-save-hook' for lens mode."
+ (lsp-lens--schedule-refresh t))
+
+(defun lsp-lens--schedule-refresh (&optional buffer-modified?)
+ "Call each of the backend.
+BUFFER-MODIFIED? determines whether the buffer was modified or
+not."
+ (-some-> lsp-lens--refresh-timer cancel-timer)
+
+ (setq lsp-lens--page (cons (window-start) (window-end)))
+ (setq lsp-lens--refresh-timer
+ (run-with-timer lsp-lens-debounce-interval
+ nil
+ #'lsp-lens-refresh
+ (or lsp-lens--modified? buffer-modified?)
+ (current-buffer))))
+
+(defun lsp-lens--schedule-refresh-modified ()
+ "Schedule a lens refresh due to a buffer-modification.
+See `lsp-lens--schedule-refresh' for details."
+ (lsp-lens--schedule-refresh t))
+
+(defun lsp-lens--keymap (command)
+ "Build the lens keymap for COMMAND."
+ (-doto (make-sparse-keymap)
+ (define-key [mouse-1] (lsp-lens--create-interactive-command command))))
+
+(defun lsp-lens--create-interactive-command (command?)
+ "Create an interactive COMMAND? for the lens.
+COMMAND? shall be an `&Command' (e.g. `&CodeLens' :command?) and
+mustn't be nil."
+ (if (functionp (lsp:command-command command?))
+ (lsp:command-command command?)
+ (lambda ()
+ (interactive)
+ (lsp--execute-command command?))))
+
+(defun lsp-lens--display (lenses)
+ "Show LENSES."
+ ;; rerender only if there are lenses which are not processed or if their count
+ ;; has changed(e. g. delete lens should trigger redisplay).
+ (let ((scroll-preserve-screen-position t))
+ (setq lsp-lens--modified? nil)
+ (when (or (-any? (-lambda ((&CodeLens :_processed processed))
+ (not processed))
+ lenses)
+ (eq (length lenses) lsp-lens--last-count)
+ (not lenses))
+ (setq lsp-lens--last-count (length lenses))
+ (mapc #'delete-overlay lsp-lens--overlays)
+ (setq lsp-lens--overlays
+ (->> lenses
+ (-filter #'lsp:code-lens-command?)
+ (--map (prog1 it (lsp-put it :_processed t)))
+ (-group-by (-compose #'lsp:position-line #'lsp:range-start #'lsp:code-lens-range))
+ (-map
+ (-lambda ((_ . lenses))
+ (let* ((sorted (-sort (-on #'< (-compose #'lsp:position-character
+ #'lsp:range-start
+ #'lsp:code-lens-range))
+ lenses))
+ (data (-map
+ (-lambda ((lens &as &CodeLens
+ :command? (command &as
+ &Command :title :_face face)))
+ (propertize
+ title
+ 'face (or face 'lsp-lens-face)
+ 'action (lsp-lens--create-interactive-command command)
+ 'pointer 'hand
+ 'mouse-face 'lsp-lens-mouse-face
+ 'local-map (lsp-lens--keymap command)))
+ sorted)))
+ (lsp-lens--show
+ (s-join (propertize "|" 'face 'lsp-lens-face) data)
+ (-> sorted cl-first lsp:code-lens-range lsp:range-start lsp--position-to-point)
+ data)))))))))
+
+(defun lsp-lens-refresh (buffer-modified? &optional buffer)
+ "Refresh lenses using lenses backend.
+BUFFER-MODIFIED? determines whether the BUFFER is modified or not."
+ (let ((buffer (or buffer (current-buffer))))
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (dolist (backend lsp-lens-backends)
+ (funcall backend buffer-modified?
+ (lambda (lenses version)
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (lsp-lens--process backend lenses version))))))))))
+
+(defun lsp-lens--process (backend lenses version)
+ "Process LENSES originated from BACKEND.
+VERSION is the version of the file. The lenses has to be
+refreshed only when all backends have reported for the same
+version."
+ (setq lsp-lens--data (or lsp-lens--data (make-hash-table)))
+ (puthash backend (cons version (append lenses nil)) lsp-lens--data)
+
+ (-let [backend-data (->> lsp-lens--data ht-values (-filter #'cl-rest))]
+ (when (and
+ (= (length lsp-lens-backends) (ht-size lsp-lens--data))
+ (seq-every-p (-lambda ((version))
+ (or (not version) (eq version lsp--cur-version)))
+ backend-data))
+ ;; display the data only when the backends have reported data for the
+ ;; current version of the file
+ (lsp-lens--display (apply #'append (-map #'cl-rest backend-data)))))
+ version)
+
+(lsp-defun lsp--lens-backend-not-loaded? ((&CodeLens :range
+ (&Range :start)
+ :command?
+ :_pending pending))
+ "Return t if LENS has to be loaded."
+ (and (< (window-start) (lsp--position-to-point start) (window-end))
+ (not command?)
+ (not pending)))
+
+(lsp-defun lsp--lens-backend-present? ((&CodeLens :range (&Range :start) :command?))
+ "Return t if LENS has to be loaded."
+ (or command?
+ (not (< (window-start) (lsp--position-to-point start) (window-end)))))
+
+(defun lsp-lens--backend-fetch-missing (lenses callback file-version)
+ "Fetch LENSES without command in for the current window.
+
+TICK is the buffer modified tick. If it does not match
+`buffer-modified-tick' at the time of receiving the updates the
+updates must be discarded..
+CALLBACK - the callback for the lenses.
+FILE-VERSION - the version of the file."
+ (seq-each
+ (lambda (it)
+ (with-lsp-workspace (lsp-get it :_workspace)
+ (lsp-put it :_pending t)
+ (lsp-put it :_workspace nil)
+ (lsp-request-async "codeLens/resolve" it
+ (-lambda ((&CodeLens :command?))
+ (lsp-put it :_pending nil)
+ (lsp-put it :command command?)
+ (when (seq-every-p #'lsp--lens-backend-present? lenses)
+ (funcall callback lenses file-version)))
+ :mode 'tick)))
+ (seq-filter #'lsp--lens-backend-not-loaded? lenses)))
+
+(defun lsp-lens--backend (modified? callback)
+ "Lenses backend using `textDocument/codeLens'.
+MODIFIED? - t when buffer is modified since the last invocation.
+CALLBACK - callback for the lenses."
+ (when (lsp--find-workspaces-for "textDocument/codeLens")
+ (if modified?
+ (progn
+ (setq lsp-lens--backend-cache nil)
+ (lsp-request-async "textDocument/codeLens"
+ `(:textDocument (:uri ,(lsp--buffer-uri)))
+ (lambda (lenses)
+ (setq lsp-lens--backend-cache
+ (seq-mapcat
+ (-lambda ((workspace . workspace-lenses))
+ ;; preserve the original workspace so we can later use it to resolve the lens
+ (seq-do (-rpartial #'lsp-put :_workspace workspace) workspace-lenses)
+ workspace-lenses)
+ lenses))
+ (if (-every? #'lsp:code-lens-command? lsp-lens--backend-cache)
+ (funcall callback lsp-lens--backend-cache lsp--cur-version)
+ (lsp-lens--backend-fetch-missing lsp-lens--backend-cache callback lsp--cur-version)))
+ :error-handler #'ignore
+ :mode 'tick
+ :no-merge t
+ :cancel-token (concat (buffer-name (current-buffer)) "-lenses")))
+ (if (-all? #'lsp--lens-backend-present? lsp-lens--backend-cache)
+ (funcall callback lsp-lens--backend-cache lsp--cur-version)
+ (lsp-lens--backend-fetch-missing lsp-lens--backend-cache callback lsp--cur-version)))))
+
+(defun lsp-lens--refresh-buffer ()
+ "Trigger lens refresh on buffer."
+ (remove-hook 'lsp-on-idle-hook #'lsp-lens--refresh-buffer t)
+ (when (bound-and-true-p lsp-lens-mode)
+ (lsp-lens-refresh t)))
+
+(defun lsp--lens-on-refresh (workspace)
+ "Clear lens within all buffers of WORKSPACE, refreshing all workspace buffers."
+ (cl-assert (not (eq nil workspace)))
+ (->> (lsp--workspace-buffers workspace)
+ (mapc (lambda (buffer)
+ (lsp-with-current-buffer buffer
+ (if (lsp--buffer-visible-p)
+ (when (bound-and-true-p lsp-lens-mode)
+ (lsp-lens-refresh t))
+ (progn
+ (add-hook 'lsp-on-idle-hook #'lsp-lens--refresh-buffer nil t)
+ (lsp--idle-reschedule (current-buffer)))))))))
+
+;;;###autoload
+(defun lsp-lens--enable ()
+ "Enable lens mode."
+ (when (and lsp-lens-enable
+ (lsp-feature? "textDocument/codeLens"))
+ (lsp-lens-mode 1)))
+
+(defun lsp-lens--disable ()
+ "Disable lens mode."
+ (lsp-lens-mode -1))
+
+;;;###autoload
+(defun lsp-lens-show ()
+ "Display lenses in the buffer."
+ (interactive)
+ (->> (lsp-request "textDocument/codeLens"
+ `(:textDocument (:uri
+ ,(lsp--path-to-uri buffer-file-name))))
+ (seq-map (-lambda ((lens &as &CodeAction :command?))
+ (if command?
+ lens
+ (lsp-request "codeLens/resolve" lens))))
+ lsp-lens--display))
+
+;;;###autoload
+(defun lsp-lens-hide ()
+ "Delete all lenses."
+ (interactive)
+ (let ((scroll-preserve-screen-position t))
+ (seq-do #'delete-overlay lsp-lens--overlays)
+ (setq lsp-lens--overlays nil)))
+
+;;;###autoload
+(define-minor-mode lsp-lens-mode
+ "Toggle code-lens overlays."
+ :group 'lsp-lens
+ :global nil
+ :init-value nil
+ :lighter " Lens"
+ (cond
+ (lsp-lens-mode
+ (add-hook 'lsp-unconfigure-hook #'lsp-lens--disable nil t)
+ (add-hook 'lsp-configure-hook #'lsp-lens--enable nil t)
+ (add-hook 'lsp-on-idle-hook #'lsp-lens--idle-function nil t)
+ (add-hook 'lsp-on-change-hook #'lsp-lens--schedule-refresh-modified nil t)
+ (add-hook 'after-save-hook #'lsp-lens--after-save nil t)
+ (add-hook 'before-revert-hook #'lsp-lens-hide nil t)
+ (lsp-lens-refresh t))
+ (t
+ (remove-hook 'lsp-on-idle-hook #'lsp-lens--idle-function t)
+ (remove-hook 'lsp-on-change-hook #'lsp-lens--schedule-refresh-modified t)
+ (remove-hook 'after-save-hook #'lsp-lens--after-save t)
+ (remove-hook 'before-revert-hook #'lsp-lens-hide t)
+ (when lsp-lens--refresh-timer
+ (cancel-timer lsp-lens--refresh-timer))
+ (setq lsp-lens--refresh-timer nil)
+ (lsp-lens-hide)
+ (setq lsp-lens--last-count nil)
+ (setq lsp-lens--backend-cache nil)
+ (remove-hook 'lsp-configure-hook #'lsp-lens--enable t)
+ (remove-hook 'lsp-unconfigure-hook #'lsp-lens--disable t))))
+
+
+;; avy integration
+
+(declare-function avy-process "ext:avy" (candidates &optional overlay-fn cleanup-fn))
+(declare-function avy--key-to-char "ext:avy" (c))
+(defvar avy-action)
+
+;;;###autoload
+(defun lsp-avy-lens ()
+ "Click lsp lens using `avy' package."
+ (interactive)
+ (unless lsp-lens--overlays
+ (user-error "No lenses in current buffer"))
+ (let* ((avy-action 'identity)
+ (position (if (eq lsp-lens-place-position 'end-of-line)
+ 'after-string
+ 'before-string))
+ (action (cl-third
+ (avy-process
+ (-mapcat
+ (lambda (overlay)
+ (-map-indexed
+ (lambda (index lens-token)
+ (list overlay index
+ (get-text-property 0 'action lens-token)))
+ (overlay-get overlay 'lsp--metadata)))
+ lsp-lens--overlays)
+ (-lambda (path ((ov index) . _win))
+ (let* ((path (mapcar #'avy--key-to-char path))
+ (str (propertize (string (car (last path)))
+ 'face 'avy-lead-face))
+ (old-str (overlay-get ov position))
+ (old-str-tokens (s-split "|" old-str))
+ (old-token (seq-elt old-str-tokens index))
+ (tokens `(,@(-take index old-str-tokens)
+ ,(-if-let ((_ prefix suffix)
+ (s-match "\\(^[[:space:]]+\\)\\(.*\\)" old-token))
+ (concat prefix str suffix)
+ (concat str old-token))
+ ,@(-drop (1+ index) old-str-tokens)))
+ (new-str (s-join (propertize "|" 'face 'lsp-lens-face) tokens))
+ (new-str (if (or (s-ends-with? "\n" new-str)
+ (eq lsp-lens-place-position 'end-of-line))
+ new-str
+ (concat new-str "\n"))))
+ (overlay-put ov position new-str)))
+ (lambda ()
+ (--map (overlay-put it position
+ (overlay-get it 'lsp-original))
+ lsp-lens--overlays))))))
+ (when action (funcall-interactively action))))
+
+(lsp-consistency-check lsp-lens)
+
+(provide 'lsp-lens)
+;;; lsp-lens.el ends here