diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/lsp-ui-20180619.251/lsp-ui-peek.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/lsp-ui-20180619.251/lsp-ui-peek.el | 731 |
1 files changed, 731 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/lsp-ui-20180619.251/lsp-ui-peek.el b/configs/shared/emacs/.emacs.d/elpa/lsp-ui-20180619.251/lsp-ui-peek.el new file mode 100644 index 000000000000..f9823b8d0f73 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/lsp-ui-20180619.251/lsp-ui-peek.el @@ -0,0 +1,731 @@ +;;; lsp-ui-peek.el --- Lsp-Ui-Peek -*- lexical-binding: t -*- + +;; Copyright (C) 2017 Sebastien Chapuis + +;; Author: Sebastien Chapuis <sebastien@chapu.is> +;; URL: https://github.com/emacs-lsp/lsp-ui +;; Keywords: lsp, ui +;; Version: 0.0.1 + +;;; License +;; +;; 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, 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; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. + +;;; Commentary: +;; +;; Load this file and execute `lsp-ui-peek-find-references' +;; on a symbol to find its references +;; or `lsp-ui-peek-find-definitions'. +;; Type 'q' to close the window. +;; + +;;; Code: + +(require 'lsp-mode) +(require 'xref) +(require 'dash) + +(defgroup lsp-ui-peek nil + "Improve version of xref with peek feature." + :group 'tools + :group 'convenience + :group 'lsp-ui + :link '(custom-manual "(lsp-ui-peek) Top") + :link '(info-link "(lsp-ui-peek) Customizing")) + +(defcustom lsp-ui-peek-enable t + "Whether or not to enable ‘lsp-ui-peek’." + :type 'boolean + :group 'lsp-ui) + +(defcustom lsp-ui-peek-peek-height 20 + "Height of the peek code." + :type 'integer + :group 'lsp-ui-peek) + +(defcustom lsp-ui-peek-list-width 50 + "Width of the right panel." + :type 'integer + :group 'lsp-ui-peek) + +(defcustom lsp-ui-peek-fontify 'on-demand + "Whether to fontify chunks of code (use semantics colors). +WARNING: 'always can heavily slow the processing when `lsp-ui-peek-expand-function' +expands more than 1 file. It is recommended to keeps the default value of +`lsp-ui-peek-expand-function' when this variable is 'always." + :type '(choice (const :tag "Never" never) + (const :tag "On demand" on-demand) + (const :tag "Always" always)) + :group 'lsp-ui-peek) + +(defcustom lsp-ui-peek-always-show nil + "Show the peek view even if there is only 1 cross reference. +By default, the peek view isn't shown if there is 1 xref." + :type 'boolean + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-peek + '((((background light)) :background "light gray") + (t :background "#031A25")) + "Face used for the peek." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-list + '((((background light)) :background "light gray") + (t :background "#181818")) + "Face used to list references." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-filename + '((((background light)) :foreground "red") + (t :foreground "dark orange")) + "Face used for the filename's reference in the list." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-line-number + '((t :foreground "grey25")) + "Line number face." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-highlight + '((((background light)) :background "dim gray" + :foreground "white" + :distant-foreground "black") + (t :background "white" + :foreground "black" + :distant-foreground "white" + :box (:line-width -1 :color "white"))) + "Face used to highlight the reference/definition. +Do not use box, underline or overline prop. If you want to use +box, use a negative value for its width. Those properties deform +the whole overlay." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-header + '((((background light)) :background "grey30" :foreground "white") + (t :background "white" :foreground "black")) + "Face used for the headers." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-footer + '((t :inherit lsp-ui-peek-header)) + "Face used for the footers. Only the background of this face is used." + :group 'lsp-ui-peek) + +(defface lsp-ui-peek-selection + '((((background light)) :background "grey30" :foreground "white") + (t :background "white" :foreground "black")) + "Face used for the current selection. +Do not use box, underline or overline prop. If you want to use +box, use a negative value for its width. Those properties +deform the whole overlay." + :group 'lsp-ui-peek) + +(defvar lsp-ui-peek-expand-function 'lsp-ui-peek--expand-buffer + "A function used to determinate which file(s) to expand in the list of xrefs. +The function takes one parameter: a list of cons where the car is the +filename and the cdr is the number of references in that file. +It should returns a list of filenames to expand. +WARNING: If you change this variable and expand more than 1 file, it is +recommended to set `lsp-ui-peek-fontify' to 'never or 'on-demand, otherwise it +will cause performances issues.") + +(defvar-local lsp-ui-peek--overlay nil) +(defvar-local lsp-ui-peek--list nil) +(defvar-local lsp-ui-peek--last-xref nil) +(defvar-local lsp-ui-peek--selection 0) +(defvar-local lsp-ui-peek--offset 0) +(defvar-local lsp-ui-peek--size-list 0) +(defvar-local lsp-ui-peek--win-start nil) +(defvar-local lsp-ui-peek--kind nil) +(defvar-local lsp-ui-peek--deactivate-keymap-fn nil) + +(defvar lsp-ui-peek--jumps (make-hash-table) + "Hashtable which stores all jumps on a per window basis.") + +(defvar evil--jumps-window-jumps) ; defined in evil-jumps.el + +(defmacro lsp-ui-peek--with-evil-jumps (&rest body) + "Make `evil-jumps.el' commands work on `lsp-ui-peek--jumps'." + (declare (indent 1)) + `(let ((evil--jumps-window-jumps lsp-ui-peek--jumps)) + ,@body)) + +(with-eval-after-load 'evil-jumps + ;; We need to jump through some hoops to prevent the byte-compiler from + ;; compiling this code. We can’t compile the code without requiring + ;; ‘evil-macros’. + (eval '(progn + (evil-define-motion lsp-ui-peek-jump-backward (count) + (lsp-ui-peek--with-evil-jumps + (evil--jump-backward count) + (run-hooks 'xref-after-return-hook))) + (evil-define-motion lsp-ui-peek-jump-forward (count) + (lsp-ui-peek--with-evil-jumps + (evil--jump-forward count) + (run-hooks 'xref-after-return-hook)))) + t)) + +(defmacro lsp-ui-peek--prop (prop &optional string) + `(get-text-property 0 ,prop (or ,string (lsp-ui-peek--get-text-selection) ""))) + +(defmacro lsp-ui-peek--add-prop (prop &optional string) + `(let ((obj (or ,string (lsp-ui-peek--get-text-selection)))) + (add-text-properties 0 (length obj) ,prop obj) + obj)) + +(defun lsp-ui-peek--truncate (len s) + (if (> (string-width s) len) + (format "%s.." (substring s 0 (- len 2))) + s)) + +(defun lsp-ui-peek--get-text-selection (&optional n) + (nth (or n lsp-ui-peek--selection) + (--remove (get-text-property 0 'lsp-ui-peek-hidden it) lsp-ui-peek--list))) + +(defun lsp-ui-peek--get-selection () + (get-text-property 0 'lsp-ui-peek (or (lsp-ui-peek--get-text-selection) ""))) + +(defun lsp-ui-peek--visual-index () + (- lsp-ui-peek--selection lsp-ui-peek--offset)) + +(defun lsp-ui-peek--make-line (index src) + (-let* (((s1 . s2) src) + (len-s1 (length s1)) + (len-s2 (length s2)) + (on-selection (= (1+ (lsp-ui-peek--visual-index)) index)) + (face-left (if (= index 0) 'lsp-ui-peek-header 'lsp-ui-peek-peek)) + (face-right (cond (on-selection 'lsp-ui-peek-selection) + ((= index 0) 'lsp-ui-peek-header) + (t 'lsp-ui-peek-list)))) + (when on-selection + (setq s2 (copy-sequence s2)) + (add-face-text-property 0 len-s2 face-right nil s2)) + (unless (get-text-property 0 'lsp-ui-peek-faced s2) + (add-face-text-property 0 len-s2 face-right t s2) + (add-text-properties 0 len-s2 '(lsp-ui-peek-faced t) s2) + (add-face-text-property 0 len-s2 'default t s2)) + (add-face-text-property 0 len-s1 face-left t s1) + (add-face-text-property 0 len-s1 'default t s1) + (concat + s1 + (propertize "_" 'face face-left 'display `(space :align-to (- right-fringe ,(1+ lsp-ui-peek-list-width)))) + " " + s2 + (propertize "_" 'face face-right 'display `(space :align-to (- right-fringe 1))) + (propertize "\n" 'face face-right)))) + +(defun lsp-ui-peek--adjust (width strings) + (-let* (((s1 . s2) strings)) + (cons (lsp-ui-peek--truncate (- width (1+ lsp-ui-peek-list-width)) s1) + (lsp-ui-peek--truncate (- lsp-ui-peek-list-width 2) s2)))) + +(defun lsp-ui-peek--make-footer () + ;; Character-only terminals don't support characters of different height + (when (display-graphic-p) + (list + (concat + (propertize " " + 'face `(:background ,(face-background 'lsp-ui-peek-footer nil t) :height 1) + 'display `(space :align-to (- right-fringe ,(1+ lsp-ui-peek-list-width)))) + (propertize " " 'face '(:height 1) + 'display `(space :align-to (- right-fringe ,lsp-ui-peek-list-width))) + (propertize " " + 'face `(:background ,(face-background 'lsp-ui-peek-footer nil t) :height 1) + 'display `(space :align-to (- right-fringe 0))) + (propertize "\n" 'face '(:height 1)) + (propertize "\n" 'face '(:height 0.5)))))) + +(defun lsp-ui-peek--peek-new (src1 src2) + (-let* ((win-width (window-text-width)) + (string (-some--> (-zip-fill "" src1 src2) + (--map (lsp-ui-peek--adjust win-width it) it) + (-map-indexed 'lsp-ui-peek--make-line it) + (-concat it (lsp-ui-peek--make-footer)))) + (next-line (line-beginning-position 2)) + (ov (or (when (overlayp lsp-ui-peek--overlay) lsp-ui-peek--overlay) + (make-overlay next-line next-line)))) + (setq lsp-ui-peek--overlay ov) + (overlay-put ov 'after-string (mapconcat 'identity string "")) + (overlay-put ov 'display-line-numbers-disable t) + (overlay-put ov 'window (get-buffer-window)))) + +(defun lsp-ui-peek--expand-buffer (files) + (if (--any? (equal (car it) buffer-file-name) files) + (list buffer-file-name) + (list (caar files)))) + +(defun lsp-ui-peek--expand (xrefs) + (let* ((to-expand (->> (--map (cons (plist-get it :file) (plist-get it :count)) xrefs) + (funcall lsp-ui-peek-expand-function))) + first) + (while (nth lsp-ui-peek--selection lsp-ui-peek--list) + (when (and (lsp-ui-peek--prop 'xrefs) + (member (lsp-ui-peek--prop 'file) to-expand)) + (unless first + (setq first (1+ lsp-ui-peek--selection))) + (lsp-ui-peek--toggle-file t)) + (setq lsp-ui-peek--selection (1+ lsp-ui-peek--selection))) + (setq lsp-ui-peek--selection (or first 0)) + (lsp-ui-peek--recenter))) + +(defun lsp-ui-peek--show (xrefs) + "Create a window to list references/defintions. +XREFS is a list of references/definitions." + (setq lsp-ui-peek--win-start (window-start) + lsp-ui-peek--selection 0 + lsp-ui-peek--offset 0 + lsp-ui-peek--size-list 0 + lsp-ui-peek--list nil) + (when (eq (logand lsp-ui-peek-peek-height 1) 1) + (setq lsp-ui-peek-peek-height (1+ lsp-ui-peek-peek-height))) + (when (< (- (line-number-at-pos (window-end)) (line-number-at-pos)) + (+ lsp-ui-peek-peek-height 3)) + (recenter 15)) + (setq xrefs (--sort (string< (plist-get it :file) (plist-get other :file)) xrefs)) + (--each xrefs + (-let* (((&plist :file filename :xrefs xrefs :count count) it) + (len-str (number-to-string count))) + (setq lsp-ui-peek--size-list (+ lsp-ui-peek--size-list count)) + (push (concat (propertize (lsp-ui--workspace-path filename) + 'face 'lsp-ui-peek-filename + 'file filename + 'xrefs xrefs) + (propertize " " 'display `(space :align-to (- right-fringe ,(1+ (length len-str))))) + (propertize len-str 'face 'lsp-ui-peek-filename)) + lsp-ui-peek--list))) + (setq lsp-ui-peek--list (nreverse lsp-ui-peek--list)) + (lsp-ui-peek--expand xrefs) + (lsp-ui-peek--peek)) + +(defun lsp-ui-peek--recenter () + (let ((half-height (/ lsp-ui-peek-peek-height 2))) + (when (> lsp-ui-peek--selection half-height) + (setq lsp-ui-peek--offset (- lsp-ui-peek--selection (1- half-height)))))) + +(defun lsp-ui-peek--fill (min-len list) + (let ((len (length list))) + (if (< len min-len) + (append list (-repeat (- min-len len) "")) + list))) + +(defun lsp-ui-peek--render (major string) + (with-temp-buffer + (insert string) + (delay-mode-hooks + (let ((inhibit-message t)) + (funcall major)) + (ignore-errors + (font-lock-ensure))) + (buffer-string))) + +(defun lsp-ui-peek--peek () + "Show reference's chunk of code." + (-let* ((xref (lsp-ui-peek--get-selection)) + ((&plist :file file :chunk chunk) (or xref lsp-ui-peek--last-xref)) + (header (concat " " (lsp-ui--workspace-path file) "\n")) + (header2 (format " %s %s" lsp-ui-peek--size-list (symbol-name lsp-ui-peek--kind))) + (ref-view (--> chunk + (if (eq lsp-ui-peek-fontify 'on-demand) + (lsp-ui-peek--render major-mode it) + chunk) + (subst-char-in-string ?\t ?\s it) + (concat header it) + (split-string it "\n"))) + (list-refs (->> lsp-ui-peek--list + (--remove (lsp-ui-peek--prop 'lsp-ui-peek-hidden it)) + (-drop lsp-ui-peek--offset) + (-take (1- lsp-ui-peek-peek-height)) + (lsp-ui-peek--fill (1- lsp-ui-peek-peek-height)) + (-concat (list header2))))) + (setq lsp-ui-peek--last-xref (or xref lsp-ui-peek--last-xref)) + (lsp-ui-peek--peek-new ref-view list-refs))) + +(defun lsp-ui-peek--toggle-text-prop (s) + (let ((state (lsp-ui-peek--prop 'lsp-ui-peek-hidden s))) + (lsp-ui-peek--add-prop `(lsp-ui-peek-hidden ,(not state)) s))) + +(defun lsp-ui-peek--toggle-hidden (file) + (setq lsp-ui-peek--list + (--map-when (string= (plist-get (lsp-ui-peek--prop 'lsp-ui-peek it) :file) file) + (prog1 it (lsp-ui-peek--toggle-text-prop it)) + lsp-ui-peek--list))) + +(defun lsp-ui-peek--remove-hidden (file) + (setq lsp-ui-peek--list + (--map-when (string= (plist-get (lsp-ui-peek--prop 'lsp-ui-peek it) :file) file) + (prog1 it (lsp-ui-peek--add-prop '(lsp-ui-peek-hidden nil) it)) + lsp-ui-peek--list))) + +(defun lsp-ui-peek--make-ref-line (xref) + (-let* (((&plist :summary summary :line line :file file) xref) + (string (format "%-3s %s" + (propertize (number-to-string (1+ line)) + 'face 'lsp-ui-peek-line-number) + (string-trim summary)))) + (lsp-ui-peek--add-prop `(lsp-ui-peek ,xref file ,file) string))) + +(defun lsp-ui-peek--insert-xrefs (xrefs filename index) + (setq lsp-ui-peek--list (--> (lsp-ui-peek--get-xrefs-in-file (cons filename xrefs)) + (-map 'lsp-ui-peek--make-ref-line it) + (-insert-at (1+ index) it lsp-ui-peek--list) + (-flatten it))) + (lsp-ui-peek--add-prop '(xrefs nil))) + +(defun lsp-ui-peek--toggle-file (&optional no-update) + (interactive) + (-if-let* ((xrefs (lsp-ui-peek--prop 'xrefs)) + (filename (lsp-ui-peek--prop 'file)) + (index (--find-index (equal (lsp-ui-peek--prop 'file it) filename) + lsp-ui-peek--list))) + (lsp-ui-peek--insert-xrefs xrefs filename index) + (let ((file (lsp-ui-peek--prop 'file))) + (lsp-ui-peek--toggle-hidden file) + (while (not (equal file (lsp-ui-peek--prop 'file))) + (lsp-ui-peek--select-prev t)))) + (unless no-update + (lsp-ui-peek--peek))) + +(defun lsp-ui-peek--select (index) + (setq lsp-ui-peek--selection (+ lsp-ui-peek--selection index))) + +(defun lsp-ui-peek--select-next (&optional no-update) + (interactive) + (when (lsp-ui-peek--get-text-selection (1+ lsp-ui-peek--selection)) + (lsp-ui-peek--select 1) + (while (> (lsp-ui-peek--visual-index) (- lsp-ui-peek-peek-height 2)) + (setq lsp-ui-peek--offset (1+ lsp-ui-peek--offset))) + (unless no-update + (lsp-ui-peek--peek)))) + +(defun lsp-ui-peek--select-prev (&optional no-update) + (interactive) + (when (> lsp-ui-peek--selection 0) + (lsp-ui-peek--select -1) + (while (< (lsp-ui-peek--visual-index) 0) + (setq lsp-ui-peek--offset (1- lsp-ui-peek--offset)))) + (unless no-update + (lsp-ui-peek--peek))) + +(defun lsp-ui-peek--skip-refs (fn) + (let ((last-file (lsp-ui-peek--prop 'file)) + last-selection) + (when (lsp-ui-peek--get-selection) + (while (and (equal (lsp-ui-peek--prop 'file) last-file) + (not (equal last-selection lsp-ui-peek--selection))) + (setq last-selection lsp-ui-peek--selection) + (funcall fn t))))) + +(defun lsp-ui-peek--select-prev-file () + (interactive) + (if (not (lsp-ui-peek--get-selection)) + (lsp-ui-peek--select-prev) + (lsp-ui-peek--skip-refs 'lsp-ui-peek--select-prev) + (when (lsp-ui-peek--get-selection) + (lsp-ui-peek--skip-refs 'lsp-ui-peek--select-prev) + (unless (= lsp-ui-peek--selection 0) + (lsp-ui-peek--select-next t)))) + (if (lsp-ui-peek--prop 'xrefs) + (lsp-ui-peek--toggle-file) + (lsp-ui-peek--remove-hidden (lsp-ui-peek--prop 'file))) + (lsp-ui-peek--select-next t) + (lsp-ui-peek--recenter) + (lsp-ui-peek--peek)) + +(defun lsp-ui-peek--select-next-file () + (interactive) + (lsp-ui-peek--skip-refs 'lsp-ui-peek--select-next) + (if (lsp-ui-peek--prop 'xrefs) + (lsp-ui-peek--toggle-file) + (lsp-ui-peek--remove-hidden (lsp-ui-peek--prop 'file))) + (lsp-ui-peek--select-next t) + (lsp-ui-peek--recenter) + (lsp-ui-peek--peek)) + +(defun lsp-ui-peek--peek-hide () + "Hide the chunk of code and restore previous state." + (when (overlayp lsp-ui-peek--overlay) + (delete-overlay lsp-ui-peek--overlay)) + (setq lsp-ui-peek--overlay nil + lsp-ui-peek--last-xref nil) + (set-window-start (get-buffer-window) lsp-ui-peek--win-start)) + +(defun lsp-ui-peek--deactivate-keymap () + "Deactivate keymap." + (-when-let (fn lsp-ui-peek--deactivate-keymap-fn) + (setq lsp-ui-peek--deactivate-keymap-fn nil) + (funcall fn))) + +(defun lsp-ui-peek--goto-xref (&optional x other-window) + "Go to a reference/definition." + (interactive) + (-if-let (xref (or x (lsp-ui-peek--get-selection))) + (-let (((&plist :file file :line line :column column) xref) + (buffer (current-buffer))) + (if (not (file-readable-p file)) + (user-error "File not readable: %s" file) + (setq lsp-ui-peek--win-start nil) + (lsp-ui-peek--abort) + (let ((marker (with-current-buffer + (or (get-file-buffer file) + (find-file-noselect file)) + (save-restriction + (widen) + (save-excursion + ;; When we jump to a file with line/column unspecified, + ;; we do not want to move the point if the buffer exists. + ;; We interpret line=column=0 differently here. + (when (> (+ line column) 0) + (goto-char 1) + (forward-line line) + (forward-char column)) + (point-marker))))) + (current-workspace lsp--cur-workspace)) + (if other-window + (pop-to-buffer (marker-buffer marker) t) + (switch-to-buffer (marker-buffer marker))) + (with-current-buffer buffer + (lsp-ui-peek-mode -1)) + (unless lsp--cur-workspace + (setq lsp--cur-workspace current-workspace)) + (unless lsp-mode + (lsp-mode 1) + (lsp-on-open)) + (goto-char marker) + (run-hooks 'xref-after-jump-hook)))) + (lsp-ui-peek--toggle-file))) + +(defun lsp-ui-peek--goto-xref-other-window () + (interactive) + (lsp-ui-peek--goto-xref nil t)) + +(defvar lsp-ui-peek-mode-map nil + "Keymap for ‘lsp-ui-peek-mode’.") +(unless lsp-ui-peek-mode-map + (let ((map (make-sparse-keymap))) + (suppress-keymap map t) + (define-key map "\e\e\e" 'lsp-ui-peek--abort) + (define-key map "\C-g" 'lsp-ui-peek--abort) + (define-key map (kbd "M-n") 'lsp-ui-peek--select-next-file) + (define-key map (kbd "<right>") 'lsp-ui-peek--select-next-file) + (define-key map (kbd "M-p") 'lsp-ui-peek--select-prev-file) + (define-key map (kbd "<left>") 'lsp-ui-peek--select-prev-file) + (define-key map (kbd "C-n") 'lsp-ui-peek--select-next) + (define-key map (kbd "n") 'lsp-ui-peek--select-next) + (define-key map (kbd "<down>") 'lsp-ui-peek--select-next) + (define-key map (kbd "C-p") 'lsp-ui-peek--select-prev) + (define-key map (kbd "p") 'lsp-ui-peek--select-prev) + (define-key map (kbd "<up>") 'lsp-ui-peek--select-prev) + (define-key map (kbd "TAB") 'lsp-ui-peek--toggle-file) + (define-key map (kbd "q") 'lsp-ui-peek--abort) + (define-key map (kbd "RET") 'lsp-ui-peek--goto-xref) + (define-key map (kbd "M-RET") 'lsp-ui-peek--goto-xref-other-window) + (setq lsp-ui-peek-mode-map map))) + +(defun lsp-ui-peek--disable () + "Do not call this function, call `lsp-ui-peek--abort' instead." + (when (bound-and-true-p lsp-ui-peek-mode) + (lsp-ui-peek-mode -1) + (lsp-ui-peek--peek-hide))) + +(defun lsp-ui-peek--abort () + (interactive) + ;; The timer fixes https://github.com/emacs-lsp/lsp-ui/issues/33 + (run-with-idle-timer 0 nil 'lsp-ui-peek--disable)) + +(define-minor-mode lsp-ui-peek-mode + "Mode for lsp-ui-peek." + :init-value nil + (if lsp-ui-peek-mode + (setq lsp-ui-peek--deactivate-keymap-fn (set-transient-map lsp-ui-peek-mode-map t 'lsp-ui-peek--abort)) + (lsp-ui-peek--deactivate-keymap) + (lsp-ui-peek--peek-hide))) + +(defun lsp-ui-peek--find-xrefs (input kind &optional request param) + "Find INPUT references. +KIND is ‘references’, ‘definitions’ or a custom kind." + (setq lsp-ui-peek--kind kind) + (let ((xrefs (lsp-ui-peek--get-references kind request param))) + (unless xrefs + (user-error "No %s found for: %s" (symbol-name kind) input)) + (xref-push-marker-stack) + (when (featurep 'evil-jumps) + (lsp-ui-peek--with-evil-jumps (evil-set-jump))) + (if (and (not lsp-ui-peek-always-show) + (not (cdr xrefs)) + (= (length (plist-get (car xrefs) :xrefs)) 1)) + (-let* ((xref (car (plist-get (car xrefs) :xrefs))) + ((&hash "uri" file "range" range) xref) + ((&hash "line" line "character" col) (gethash "start" range)) + (file (lsp--uri-to-path file))) + (lsp-ui-peek--goto-xref `(:file ,file :line ,line :column ,col))) + (lsp-ui-peek-mode) + (lsp-ui-peek--show xrefs)))) + +(defun lsp-ui-peek-find-references () + "Find references to the IDENTIFIER at point." + (interactive) + (lsp-ui-peek--find-xrefs (symbol-at-point) + 'references + "textDocument/references" + (lsp--make-reference-params))) + +(defun lsp-ui-peek-find-definitions () + "Find definitions to the IDENTIFIER at point." + (interactive) + (lsp-ui-peek--find-xrefs (symbol-at-point) + 'definitions + "textDocument/definition")) + +(defun lsp-ui-peek-find-implementation () + "Find implementation locations of the symbol at point." + (interactive) + (lsp-ui-peek--find-xrefs (symbol-at-point) + 'implementation + "textDocument/implementation")) + +(defun lsp-ui-peek-find-workspace-symbol (pattern) + "Find symbols in the worskpace. +The symbols are found matching PATTERN." + (interactive (list (read-string "workspace/symbol: " + nil 'xref--read-pattern-history))) + (lsp-ui-peek--find-xrefs pattern + 'symbols + "workspace/symbol" + (list :query pattern))) + +(defun lsp-ui-peek-find-custom (kind request &optional param) + "Find custom references. +KIND is a symbol to name the references (definition, reference, ..). +REQUEST is the method string to send the the language server. +PARAM is the method parameter. If nil, it default to TextDocumentPositionParams." + (lsp-ui-peek--find-xrefs (symbol-at-point) kind request param)) + +(defun lsp-ui-peek--extract-chunk-from-buffer (pos start end) + "Return the chunk of code pointed to by POS (a Position object) in the current buffer. +START and END are delimiters." + (let* ((point (lsp--position-to-point pos)) + (inhibit-field-text-motion t) + (line-start (1+ (- 1 (/ lsp-ui-peek-peek-height 2)))) + (line-end (/ lsp-ui-peek-peek-height 2))) + (save-excursion + (goto-char point) + (let* ((before (buffer-substring (line-beginning-position line-start) (line-beginning-position))) + (line (buffer-substring (line-beginning-position) (line-end-position))) + (after (buffer-substring (line-end-position) (line-end-position line-end))) + (len (length line))) + (add-face-text-property (max (min start len) 0) + (max (min end len) 0) + 'lsp-ui-peek-highlight t line) + `(,line . ,(concat before line after)))))) + +(defun lsp-ui-peek--xref-make-item (filename location) + "Return an item from a LOCATION in FILENAME. +LOCATION can be either a LSP Location or SymbolInformation." + ;; TODO: Read more informations from SymbolInformation. + ;; For now, only the location is used. + (-let* ((location (or (gethash "location" location) location)) + (range (gethash "range" location)) + ((&hash "start" pos-start "end" pos-end) range) + (start (gethash "character" pos-start)) + (end (gethash "character" pos-end)) + ((line . chunk) (lsp-ui-peek--extract-chunk-from-buffer pos-start start end))) + (list :summary (or line filename) + :chunk (or chunk filename) + :file filename + :line (gethash "line" pos-start) + :column start + :len (- end start)))) + +(defun lsp-ui-peek--fontify-buffer (filename) + (when (eq lsp-ui-peek-fontify 'always) + (unless buffer-file-name + (make-local-variable 'delay-mode-hooks) + (let ((buffer-file-name filename) + (enable-local-variables nil) + (inhibit-message t) + (delay-mode-hooks t)) + (set-auto-mode))) + (font-lock-ensure))) + +(defun lsp-ui-peek--get-xrefs-in-file (file) + "Return all references that contain a file. +FILE is a cons where its car is the filename and the cdr is a list of Locations +within the file. We open and/or create the file/buffer only once for all +references. The function returns a list of `ls-xref-item'." + (let* ((filename (car file)) + (visiting (find-buffer-visiting filename)) + (fn (lambda (loc) (lsp-ui-peek--xref-make-item filename loc)))) + (cond + (visiting + (with-temp-buffer + (insert-buffer-substring-no-properties visiting) + (lsp-ui-peek--fontify-buffer filename) + (mapcar fn (cdr file)))) + ((file-readable-p filename) + (with-temp-buffer + (insert-file-contents-literally filename) + (lsp-ui-peek--fontify-buffer filename) + (mapcar fn (cdr file)))) + (t (user-error "Cannot read %s" filename))))) + +(defun lsp-ui-peek--get-xrefs-list (file) + "Return a list of xrefs in FILE." + (-let* (((filename . xrefs) file)) + `(:file ,filename :xrefs ,xrefs :count ,(length xrefs)))) + +(defun lsp-ui-peek--locations-to-xref-items (locations) + "Return a list of list of item from LOCATIONS. +LOCATIONS is an array of Location objects: + +interface Location { + uri: DocumentUri; + range: Range; +}" + (-some--> (lambda (loc) (lsp--uri-to-path (gethash "uri" (or (gethash "location" loc) loc)))) + (seq-group-by it locations) + (mapcar #'lsp-ui-peek--get-xrefs-list it))) + +(defun lsp-ui-peek--to-sequence (maybe-sequence) + "If maybe-sequence is not a sequence, wraps it into a single-element sequence." + (if (sequencep maybe-sequence) maybe-sequence (list maybe-sequence))) + +(defun lsp-ui-peek--get-references (_kind request &optional param) + "Get all references/definitions for the symbol under point. +Returns item(s)." + (-some->> (lsp--send-request (lsp--make-request + request + (or param (lsp--text-document-position-params)))) + ;; Language servers may return a single LOCATION instead of a sequence of them. + (lsp-ui-peek--to-sequence) + (lsp-ui-peek--locations-to-xref-items) + (-filter 'identity))) + +(defvar lsp-ui-mode-map) + +(defun lsp-ui-peek-enable (_enable) + (interactive) + (unless (bound-and-true-p lsp-ui-mode-map) + (user-error "Please load lsp-ui before trying to enable lsp-ui-peek"))) + +;; lsp-ui.el loads lsp-ui-peek.el, so we can’t ‘require’ lsp-ui. +;; FIXME: Remove this cyclic dependency. +(declare-function lsp-ui--workspace-path "lsp-ui" (path)) + +(declare-function evil-set-jump "evil-jumps.el" (&optional pos)) + +(provide 'lsp-ui-peek) +;;; lsp-ui-peek.el ends here |