diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180911.1829/lsp-methods.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180911.1829/lsp-methods.el | 2216 |
1 files changed, 0 insertions, 2216 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180911.1829/lsp-methods.el b/configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180911.1829/lsp-methods.el deleted file mode 100644 index f03fcc909226..000000000000 --- a/configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180911.1829/lsp-methods.el +++ /dev/null @@ -1,2216 +0,0 @@ -;; Copyright (C) 2016-2018 Vibhav Pant <vibhavp@gmail.com> -*- lexical-binding: t -*- - -;; 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 <http://www.gnu.org/licenses/>. - -(require 'cl-lib) -(require 'json) -(require 'xref) -(require 'subr-x) -(require 'widget) -(require 'lsp-io) -(require 'lsp-common) -(require 'pcase) -(require 'inline) -(require 'em-glob) - -(defconst lsp--file-change-type - `((created . 1) - (changed . 2) - (deleted . 3))) - -;; A ‘lsp--client’ object describes the client-side behavior of a language -;; server. It is used to start individual server processes, each of which is -;; represented by a ‘lsp--workspace’ object. Client objects are normally -;; created using ‘lsp-define-stdio-client’ or ‘lsp-define-tcp-client’. Each -;; workspace refers to exactly one client, but there can be multiple workspaces -;; for a single client. -(cl-defstruct lsp--client - ;; ‘language-id’ is a function that receives a buffer as a single argument - ;; and should return the language identifier for that buffer. See - ;; https://microsoft.github.io/language-server-protocol/specification#textdocumentitem - ;; for a list of language identifiers. Also consult the documentation for - ;; the language server represented by this client to find out what language - ;; identifiers it supports or expects. - (language-id nil :read-only t) - - ;; send-async and send-sync are unused field, but haven't been - ;; removed so as to avoid breaking byte-compiled clients. - ;; FIXME: We shouldn’t need to take binary compatibility into account, - ;; especially since the ‘lsp--client’ structure is internal. These fields - ;; should just be removed. - (send-sync nil :read-only t) - (send-async nil :read-only t) - - ;; FIXME: This field is apparently unused and should be removed. - (type nil :read-only t) - - ;; ‘new-connection’ is a function that should start a language server process - ;; and return a cons (COMMAND-PROCESS . COMMUNICATION-PROCESS). - ;; COMMAND-PROCESS must be a process object representing the server process - ;; just started. COMMUNICATION-PROCESS must be a process (including pipe and - ;; network processes) that ‘lsp-mode’ uses to communicate with the language - ;; server using the language server protocol. COMMAND-PROCESS and - ;; COMMUNICATION-PROCESS may be the same process; in that case - ;; ‘new-connection’ may also return that process as a single - ;; object. ‘new-connection’ is called with two arguments, FILTER and - ;; SENTINEL. FILTER should be used as process filter for - ;; COMMUNICATION-PROCESS, and SENTINEL should be used as process sentinel for - ;; COMMAND-PROCESS. - (new-connection nil :read-only t) - - ;; ‘stderr’ is the name of a buffer to write the standard error to. - ;; FIXME: ‘stderr’ should be the actual buffer, and it should be a field of - ;; the ‘lsp--workspace’. - (stderr nil :read-only t) - - ;; ‘get-root’ is a function that should return the workspace root directory - ;; for the current buffer. It may return either a directory name or a - ;; directory file name. The ‘get-root’ function is called without arguments. - ;; ‘lsp-mode’ will start one server process per client and root directory. - ;; It passes the root directory to the ‘initialize’ method of the language - ;; server; see - ;; https://microsoft.github.io/language-server-protocol/specification#initialize. - ;; Also consult the documentation of your language server for information - ;; about what it expects as workspace root. - (get-root nil :read-only t) - - ;; ‘ignore-regexps’ is a list of regexps. When a data packet from the - ;; language server matches any of these regexps, it will be ignored. This is - ;; intended for dealing with language servers that output non-protocol data. - (ignore-regexps nil :read-only t) - - ;; ‘ignore-messages’ is a list of regexps. When a message from the language - ;; server matches any of these regexps, it will be ignored. This is useful - ;; for filtering out unwanted messages; such as servers that send nonstandard - ;; message types, or extraneous log messages. - (ignore-messages nil :read-only t) - - ;; ‘notification-handlers’ is a hash table mapping notification method names - ;; (strings) to functions handling the respective notifications. Upon - ;; receiving a notification, ‘lsp-mode’ will call the associated handler - ;; function passing two arguments, the ‘lsp--workspace’ object and the - ;; deserialized notification parameters. - (notification-handlers (make-hash-table :test 'equal) :read-only t) - - ;; ‘request-handlers’ is a hash table mapping request method names - ;; (strings) to functions handling the respective notifications. Upon - ;; receiving a request, ‘lsp-mode’ will call the associated handler function - ;; passing two arguments, the ‘lsp--workspace’ object and the deserialized - ;; request parameters. - (request-handlers (make-hash-table :test 'equal) :read-only t) - - ;; ‘response-handlers’ is a hash table mapping integral JSON-RPC request - ;; identifiers for pending asynchronous requests to functions handling the - ;; respective responses. Upon receiving a response from the language server, - ;; ‘lsp-mode’ will call the associated response handler function with a - ;; single argument, the deserialized response parameters. - (response-handlers (make-hash-table :test 'eql) :read-only t) - - ;; ‘string-renderers’ is an alist mapping MarkedString language identifiers - ;; (see - ;; https://microsoft.github.io/language-server-protocol/specification#textDocument_hover) - ;; to functions that can render the respective languages. The rendering - ;; functions are called with a single argument, the MarkedString value. They - ;; should return a propertized string with the rendered output. - (string-renderers '()) - ;; ‘last-id’ is the last JSON-RPC identifier used. - ;; FIXME: ‘last-id’ should be in ‘lsp--workspace’. - (last-id 0) - - ;; Function to enable the client for the current buffer, called without - ;; arguments. - (enable-function nil :read-only t) - - ;; ‘prefix-function’ is called for getting the prefix for completion. - ;; The function takes no parameter and returns a cons (start . end) representing - ;; the start and end bounds of the prefix. If it's not set, the client uses a - ;; default prefix function." - (prefix-function nil :read-only t) - - ;; Contains mapping of scheme to the function that is going to be used to load - ;; the file. - (uri-handlers (make-hash-table :test #'equal) :read-only t) - ;; ‘action-handlers’ is a hash table mapping action to a handler function. It - ;; can be used in `lsp-execute-code-action' to determine whether the action - ;; current client is interested in executing the action instead of sending it - ;; to the server. - (action-handlers (make-hash-table :test 'equal) :read-only t) - - ;; ‘default-renderer’ is the renderer that is going to be used when there is - ;; no concrete "language" specified for the current MarkedString. (see - ;; https://microsoft.github.io/language-server-protocol/specification#textDocument_hover) - (default-renderer nil)) - -(cl-defstruct lsp--registered-capability - (id "" :type string) - (method " " :type string) - (options nil)) - -;; A ‘lsp--workspace’ object represents exactly one language server process. -(cl-defstruct lsp--workspace - ;; ‘parser’ is a ‘lsp--parser’ object used to parse messages for this - ;; workspace. Parsers are not shared between workspaces. - (parser nil :read-only t) - - ;; ‘file-versions’ is a hashtable of files "owned" by the workspace. It maps - ;; file names to file versions. See - ;; https://microsoft.github.io/language-server-protocol/specification#versionedtextdocumentidentifier. - (file-versions nil :read-only t) - - ;; ‘server-capabilities’ is a hash table of the language server capabilities. - ;; It is the hash table representation of a LSP ServerCapabilities structure; - ;; cf. https://microsoft.github.io/language-server-protocol/specification#initialize. - (server-capabilities nil) - - ;; ‘registered-server-capabilities’ is a list of hash tables that represent - ;; dynamically-registered Registration objects. See - ;; https://microsoft.github.io/language-server-protocol/specification#client_registerCapability. - (registered-server-capabilities nil) - - ;; ‘root’ is a directory name or a directory file name for the workspace - ;; root. ‘lsp-mode’ passes this directory to the ‘initialize’ method of the - ;; language server; see - ;; https://microsoft.github.io/language-server-protocol/specification#initialize. - (root nil :ready-only t) - - ;; ‘client’ is the ‘lsp--client’ object associated with this workspace. - (client nil :read-only t) - - ;; FIXME: ‘change-timer-disabled’ is unused and should be removed. - (change-timer-disabled nil) - - ;; ‘proc’ is a process object; it may represent a regular process, a pipe, or - ;; a network connection. ‘lsp-mode’ communicates with ‘proc’ using the - ;; language server protocol. ‘proc’ corresponds to the COMMUNICATION-PROCESS - ;; element of the return value of the client’s ‘get-root’ field, which see. - (proc nil) - - ;; ‘proc’ is a process object; it must represent a regular process, not a - ;; pipe or network process. It represents the actual server process that - ;; corresponds to this workspace. ‘cmd-proc’ corresponds to the - ;; COMMAND-PROCESS element of the return value of the client’s ‘get-root’ - ;; field, which see. - (cmd-proc nil) - - ;; ‘buffers’ is a list of buffers associated with this workspace. - (buffers nil) - - ;; ‘highlight-overlays’ is a hash table mapping buffers to a list of overlays - ;; used for highlighting the symbol under point. - (highlight-overlays (make-hash-table :test 'eq) :read-only t) - - ;; Extra client capabilities provided by third-party packages using - ;; `lsp-register-client-capabilities'. It's value is an alist of (PACKAGE-NAME - ;; . CAPS), where PACKAGE-NAME is a symbol of the third-party package name, - ;; and CAPS is either a plist of the client capabilities, or a function that - ;; takes no argument and returns a plist of the client capabilities or nil.") - (extra-client-capabilities nil) - - ;; Workspace status - (status nil) - - ;; ‘metadata’ is a generic storage for workspace specific data. It is - ;; accessed via `lsp-workspace-set-metadata' and `lsp-workspace-set-metadata' - (metadata (make-hash-table :test 'equal)) - - ;; contains all the file notification watches that have been created for the - ;; current workspace in format filePath->file notification handle. - (watches (make-hash-table :test 'equal))) - -(defvar lsp--workspaces (make-hash-table :test #'equal) - "Table of known workspaces, indexed by the project root directory.") - -(defvar lsp--ignored-workspace-roots (make-hash-table :test #'equal) - "Table of project roots which should not have a workspace, -indexed by the project root directory. - -This is populated when the user declines to open a workspace -for a file in the workspace.") - -(defcustom lsp-render-markdown-markup-content nil - "Function to be use for rendering MarkupContent. - -It should take two arguments - a string denoting the type of markup content -and a string containing the text to be rendered. The returned value should -be a string that may be fontified/propertized. - -When nil, MarkupContent is rendered as plain text." - :type 'function - :group 'lsp-mode) - -(defcustom lsp-before-initialize-hook nil - "List of functions to be called before a Language Server has been initialized -for a new workspace." - :type 'hook - :group 'lsp-mode) - -(defcustom lsp-after-initialize-hook nil - "List of functions to be called after a Language Server has been initialized -for a new workspace." - :type 'hook - :group 'lsp-mode) - -(defcustom lsp-before-open-hook nil - "List of functions to be called before a new file with LSP support is opened." - :type 'hook - :group 'lsp-mode) - -(defcustom lsp-after-open-hook nil - "List of functions to be called after a new file with LSP support is opened." - :type 'hook - :group 'lsp-mode) - -(defvar lsp--sync-methods - '((0 . none) - (1 . full) - (2 . incremental))) -(defvar-local lsp--server-sync-method nil - "Sync method recommended by the server.") - -;;;###autoload -(defgroup lsp-mode nil - "Customization group for ‘lsp-mode’." - :group 'tools) - -;;;###autoload -(defgroup lsp-faces nil - "Faces for ‘lsp-mode’." - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-document-sync-method nil - "How to sync the document with the language server." - :type '(choice (const :tag "Documents should not be synced at all." 'none) - (const :tag "Documents are synced by always sending the full content of the document." 'full) - (const :tag "Documents are synced by always sending incremental changes to the document." 'incremental) - (const :tag "Use the method recommended by the language server." nil)) - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-project-blacklist nil - "A list of project directory regexps for which LSP shouldn't be initialized. -LSP should be initialized if the given project root matches one pattern in the -whitelist, or does not match any pattern in the blacklist." - :type '(repeat regexp) - :group 'lsp-mode) - -(defcustom lsp-project-whitelist nil - "A list of project directory regexps for which LSP should be initialized." - :type '(repeat regexp) - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-enable-eldoc t - "Enable `eldoc-mode' integration." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-eldoc-render-all t - "Define whether all of the returned by document/onHover will be displayed. - -If `lsp-markup-display-all' is set to nil `eldoc' will show only -the symbol information." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-highlight-symbol-at-point t - "Highlight the symbol under the point." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-enable-codeaction t - "Enable code action processing." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-enable-completion-at-point t - "Enable `completion-at-point' integration." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-enable-xref t - "Enable xref integration." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-enable-indentation t - "Indent regions using the file formatting functionality provided by the language server." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-before-save-edits t - "If non-nil, `lsp-mode' will apply edits suggested by the language server -before saving a document." - :type 'boolean - :group 'lsp-mode) - -;;;###autoload -(defcustom lsp-hover-text-function 'lsp--text-document-hover-string - "The LSP method to use to display text on hover." - :type '(choice (function :tag "textDocument/hover" - lsp--text-document-hover-string) - (function :tag "textDocument/signatureHelp" - lsp--text-document-signature-help)) - :group 'lsp-mode) - -;;;###autoload -(defface lsp-face-highlight-textual - '((((background dark)) :background "saddle brown") - (((background light)) :background "yellow")) - "Face used for textual occurances of symbols." - :group 'lsp-faces) - -;;;###autoload -(defface lsp-face-highlight-read - '((((background dark)) :background "firebrick") - (((background light)) :background "red")) - "Face used for highlighting symbols being read." - :group 'lsp-faces) - -;;;###autoload -(defface lsp-face-highlight-write - '((((background dark)) :background "sea green") - (((background light)) :background "green")) - "Face used for highlighting symbols being written to." - :group 'lsp-faces) - -(defun lsp-client-register-uri-handler (client scheme handler) - (cl-check-type client lsp--client) - (cl-check-type scheme string) - (cl-check-type handler function) - (puthash scheme handler (lsp--client-uri-handlers client))) - -(defun lsp-client-on-notification (client method callback) - (cl-check-type client lsp--client) - (cl-check-type method string) - (cl-check-type callback function) - (puthash method callback (lsp--client-notification-handlers client))) - -(defun lsp-client-on-request (client method callback) - (cl-check-type client lsp--client) - (cl-check-type method string) - (cl-check-type callback function) - (puthash method callback (lsp--client-request-handlers client))) - -(defun lsp-client-on-action (client method callback) - (cl-check-type client lsp--client) - (cl-check-type method string) - (cl-check-type callback function) - (puthash method callback (lsp--client-action-handlers client))) - -(defun lsp-workspace-set-metadata (key value &optional workspace) - "Associate KEY with VALUE in the WORKSPACE metadata. -If WORKSPACE is not provided current workspace will be used." - (puthash key value (lsp--workspace-metadata (or workspace lsp--cur-workspace)))) - -(defun lsp-workspace-get-metadata (key &optional workspace) - "Lookup KEY in WORKSPACE metadata. -If WORKSPACE is not provided current workspace will be used." - (gethash key (lsp--workspace-metadata (or workspace lsp--cur-workspace)))) - -(define-inline lsp--make-request (method &optional params) - "Create request body for method METHOD and parameters PARAMS." - (inline-quote - (plist-put (lsp--make-notification ,method ,params) - :id (cl-incf (lsp--client-last-id (lsp--workspace-client lsp--cur-workspace)))))) - -(defun lsp-make-request (method &optional params) - "Create request body for method METHOD and parameters PARAMS." - (lsp--make-request method params)) - -(defun lsp--make-response-error (code message data) - (cl-check-type code number) - (cl-check-type message string) - `(:code ,code :message ,message :data ,data)) - -(defun lsp--make-response (id result error) - (cl-check-type error list) - `(:jsonrpc "2.0" :id ,id :result ,result :error ,error)) - -(define-inline lsp--make-notification (method &optional params) - "Create notification body for method METHOD and parameters PARAMS." - (inline-quote - (progn (cl-check-type ,method string) - (list :jsonrpc "2.0" :method ,method :params ,params)))) - -;; Define non-inline public aliases to avoid breaking binary compatibility. -(defun lsp-make-notification (method &optional params) - "Create notification body for method METHOD and parameters PARAMS." - (lsp--make-notification method params)) - -(define-inline lsp--make-message (params) - "Create a LSP message from PARAMS, after encoding it to a JSON string." - (inline-quote - (let* ((json-encoding-pretty-print lsp-print-io) - (json-false :json-false) - (body (json-encode ,params))) - (format "Content-Length: %d\r\n\r\n%s" (string-bytes body) body)))) - -(define-inline lsp--send-notification (body) - "Send BODY as a notification to the language server." - (inline-quote - (lsp--send-no-wait - (lsp--make-message ,body) - (lsp--workspace-proc lsp--cur-workspace)))) - -(defun lsp-send-notification (body) - "Send BODY as a notification to the language server." - (lsp--send-notification body)) - -(define-inline lsp--cur-workspace-check () - (inline-quote - (progn - (cl-assert lsp--cur-workspace nil - "No language server is associated with this buffer.") - (cl-assert (lsp--workspace-p lsp--cur-workspace))))) - -(define-inline lsp--cur-parser () - (inline-quote (lsp--workspace-parser lsp--cur-workspace))) - -(defun lsp--send-request (body &optional no-wait) - "Send BODY as a request to the language server, get the response. -If NO-WAIT is non-nil, don't synchronously wait for a response." - (let* ((parser (lsp--cur-parser)) - (message (lsp--make-message body)) - (process (lsp--workspace-proc lsp--cur-workspace))) - (setf (lsp--parser-waiting-for-response parser) (not no-wait)) - (if no-wait - (lsp--send-no-wait message process) - (lsp--send-wait message process parser)) - (when (not no-wait) - (prog1 (lsp--parser-response-result parser) - (setf (lsp--parser-response-result parser) nil))))) - -(defalias 'lsp-send-request 'lsp--send-request - "Send BODY as a request to the language server and return the response synchronously. - -\n(fn BODY)") - -(defun lsp--send-request-async (body callback) - "Send BODY as a request to the language server, and call CALLBACK with -the response recevied from the server asynchronously." - (let ((client (lsp--workspace-client lsp--cur-workspace)) - (id (plist-get body :id))) - (cl-assert id nil "body missing id field") - (puthash id callback (lsp--client-response-handlers client)) - (lsp--send-no-wait (lsp--make-message body) - (lsp--workspace-proc lsp--cur-workspace)) - body)) - -(defalias 'lsp-send-request-async 'lsp--send-request-async) - -(define-inline lsp--inc-cur-file-version () - (inline-quote (cl-incf (gethash (current-buffer) - (lsp--workspace-file-versions lsp--cur-workspace))))) - -(define-inline lsp--cur-file-version () - "Return the file version number. If INC, increment it before." - (inline-quote - (gethash (current-buffer) (lsp--workspace-file-versions lsp--cur-workspace)))) - -(define-inline lsp--make-text-document-item () - "Make TextDocumentItem for the currently opened file. - -interface TextDocumentItem { - uri: string; // The text document's URI - languageId: string; // The text document's language identifier. - version: number; - text: string; -}" - (inline-quote - (let ((language-id-fn (lsp--client-language-id (lsp--workspace-client lsp--cur-workspace)))) - (list :uri (lsp--buffer-uri) - :languageId (funcall language-id-fn (current-buffer)) - :version (lsp--cur-file-version) - :text (buffer-substring-no-properties (point-min) (point-max)))))) - -;; Clean up the entire state of lsp mode when Emacs is killed, to get rid of any -;; pending language servers. -(add-hook 'kill-emacs-hook #'lsp--global-teardown) - -(defun lsp--global-teardown () - (with-demoted-errors "Error in ‘lsp--global-teardown’: %S" - (maphash (lambda (_k value) (lsp--teardown-workspace value)) lsp--workspaces))) - -(defun lsp--teardown-workspace (workspace) - (setq lsp--cur-workspace workspace) - (lsp--shutdown-cur-workspace)) - -(defun lsp--shutdown-cur-workspace () - "Shut down the language server process for ‘lsp--cur-workspace’." - (with-demoted-errors "LSP error: %S" - (lsp--send-request (lsp--make-request "shutdown" (make-hash-table)) t) - (lsp--send-notification (lsp--make-notification "exit" nil))) - (lsp--uninitialize-workspace)) - -(defun lsp--uninitialize-workspace () - "When a workspace is shut down, by request or from just -disappearing, unset all the variables related to it." - (lsp-kill-watch (lsp--workspace-watches lsp--cur-workspace)) - - (let (proc - (root (lsp--workspace-root lsp--cur-workspace))) - (with-current-buffer (current-buffer) - (setq proc (lsp--workspace-proc lsp--cur-workspace)) - (if (process-live-p proc) - (kill-process (lsp--workspace-proc lsp--cur-workspace))) - (setq lsp--cur-workspace nil) - (lsp--unset-variables) - (kill-local-variable 'lsp--cur-workspace)) - (remhash root lsp--workspaces))) - -(defun lsp-restart-workspace () - "Shut down and then restart the current workspace. -This involves uninitializing each of the buffers associated with -the workspace, closing the process managing communication with -the client, and then starting up again." - (interactive) - (when (and (lsp-mode) (buffer-file-name) lsp--cur-workspace) - (let ((old-buffers (lsp--workspace-buffers lsp--cur-workspace)) - (restart (lsp--client-enable-function (lsp--workspace-client lsp--cur-workspace))) - (proc (lsp--workspace-proc lsp--cur-workspace))) - (lsp--remove-cur-overlays) - ;; Shut down the LSP mode for each buffer in the workspace - (dolist (buffer old-buffers) - (with-current-buffer buffer - (lsp--text-document-did-close) - (setq lsp--cur-workspace nil) - (lsp-mode -1))) - - ;; Let the process actually shut down - (while (process-live-p proc) - (accept-process-output proc)) - - ;; Re-enable LSP mode for each buffer - (dolist (buffer old-buffers) - (with-current-buffer buffer - (funcall restart)))))) - -;; NOTE: Possibly make this function subject to a setting, if older LSP servers -;; are unhappy -(defun lsp--client-capabilities () - "Return the client capabilites." - (apply #'lsp--merge-plists - `(:workspace ,(lsp--client-workspace-capabilities) - :textDocument ,(lsp--client-textdocument-capabilities)) - (seq-map (lambda (extra-capabilities-cons) - (let* ((package-name (car extra-capabilities-cons)) - (value (cdr extra-capabilities-cons)) - (capabilities (if (functionp value) (funcall value) - value))) - (if (and capabilities (not (listp capabilities))) - (progn - (message "Capabilities provided by %s are not a plist: %s" package-name value) - nil) - capabilities))) - (lsp--workspace-extra-client-capabilities lsp--cur-workspace)))) - -(defun lsp--merge-plists (first &rest rest) - "Deeply merge plists. - -FIRST is the plist to be merged into. The rest of the arguments -can be either plists or nil. The non-nil plists in the rest of -the arguments will be merged into FIRST. - -Return the merged plist." - (cl-check-type first list) - (seq-each - (lambda (pl) (setq first (lsp--merge-two-plists first pl))) - rest) - first) - -(defun lsp--merge-two-plists (first second) - "Deeply merge two plists. - -All values in SECOND are merged into FIRST. FIRST can be nil or -a plist. SECOND must be a plist. - -Return the merged plist." - (when second - (if (not (listp second)) - (warn "Cannot merge non-list value into a plist. The value is %s" second) - (cl-loop for (key second-value) on second - collect (progn - (let ((first-value (plist-get first key)) - merged-value) - (cond - ((null second-value)) ; do nothing - ((null first-value) - (if (listp second-value) - ;; Deep copy second-value so that the original value won't - ;; be modified. - (setq merged-value - (lsp--merge-two-plists nil second-value))) - (setq merged-value second-value)) - ((and (listp first-value) (listp second-value)) - (setq merged-value (lsp--merge-two-plists first-value second-value))) - ;; Otherwise, the first value is a leaf entry and should - ;; not be overridden. - ) - (when merged-value - (setq first (plist-put first key merged-value)))))))) - first) - -(defun lsp--server-register-capability (reg) - (lsp--cur-workspace-check) - (let ((method (gethash "method" reg))) - (push - (make-lsp--registered-capability - :id (gethash "id" reg) - :method method - :options (gethash "registerOptions" reg)) - (lsp--workspace-registered-server-capabilities lsp--cur-workspace)))) - -(defun lsp--server-unregister-capability (unreg) - (let* ((id (gethash "id" unreg)) - (fn (lambda (e) (equal (lsp--registered-capability-id e) id)))) - (setf (lsp--workspace-registered-server-capabilities lsp--cur-workspace) - (seq-remove fn - (lsp--workspace-registered-server-capabilities lsp--cur-workspace))))) - -(defun lsp--client-workspace-capabilities () - "Client Workspace capabilities according to LSP." - `(:applyEdit t - :executeCommand (:dynamicRegistration t))) - -(defun lsp--client-textdocument-capabilities () - "Client Text document capabilities according to LSP." - `(:synchronization (:willSave t :didSave t :willSaveWaitUntil t) - :symbol (:symbolKind (:valueSet ,(cl-coerce (cl-loop for kind from 1 to 25 collect kind) 'vector))) - :formatting (:dynamicRegistration t) - :codeAction (:dynamicRegistration t))) - -(defun lsp-register-client-capabilities (package-name caps) - "Register extra client capabilities for the current workspace. - -This function must be called before the initialize request is -sent. It's recommended to to call it in the -`lsp-before-initialize-hook'. - -PACKAGE name is the symbol of the name of the package that -registers the capabilities. CAPS is either a plist of the -capabilities, or a function that takes no argument and return a -plist of the client capabilties or nil. - -Registered capabilities are merged into the default capabilities -before sending to the server via the initialize request. If two -packages provide different values for the same leaf capability -entry, the value is set to the one that registers later. Default -leaf capability entries can not be overwritten." - (lsp--cur-workspace-check) - (cl-check-type package-name symbolp) - (cl-check-type caps (or list function)) - (let ((extra-client-capabilities - (lsp--workspace-extra-client-capabilities lsp--cur-workspace))) - (if (alist-get package-name extra-client-capabilities) - (message "%s has already registered client capabilities" package-name) - (push `(,package-name . ,caps) - (lsp--workspace-extra-client-capabilities lsp--cur-workspace))))) - -(defun lsp-unregister-client-capabilities (package-name) - "Unregister extra capabilities provided by PACKAGE-NAME for the current workspace. - -PACKAGE-NAME is a symbol of the name of the package that has -registered client capabilities by calling -`lsp-register-client-capabilities'." - (lsp--cur-workspace-check) - (cl-check-type package-name symbol) - (let ((extra-client-capabilities - (lsp--workspace-extra-client-capabilities lsp--cur-workspace))) - (setf (lsp--workspace-extra-client-capabilities lsp--cur-workspace) - (assq-delete-all package-name extra-client-capabilities)))) - -(define-inline lsp--server-capabilities () - "Return the capabilities of the language server associated with the buffer." - (inline-quote (lsp--workspace-server-capabilities lsp--cur-workspace))) - -(defun lsp--server-has-sync-options-p () - "Return whether the server has a TextDocumentSyncOptions object in -ServerCapabilities.textDocumentSync." - (hash-table-p (gethash "textDocumentSync" (lsp--server-capabilities)))) - -(defun lsp--send-open-close-p () - "Return whether open and close notifications should be sent to the server." - (let ((sync (gethash "textDocumentSync" (lsp--server-capabilities)))) - (and (hash-table-p sync) - (gethash "openClose" sync)))) - -(defun lsp--send-will-save-p () - "Return whether will save notifications should be sent to the server." - (let ((sync (gethash "textDocumentSync" (lsp--server-capabilities)))) - (and (hash-table-p sync) - (gethash "willSave" sync)))) - -(defun lsp--send-will-save-wait-until-p () - "Return whether will save wait until notifications should be sent to the server." - (let ((sync (gethash "textDocumentSync" (lsp--server-capabilities)))) - (and (hash-table-p sync) - (gethash "willSaveWaitUntil" sync)))) - -(defun lsp--save-include-text-p () - "Return whether save notifications should include the text document's contents." - (let ((sync (gethash "textDocumentSync" (lsp--server-capabilities)))) - (and (hash-table-p sync) - (hash-table-p (gethash "save" sync nil)) - (gethash "includeText" (gethash "save" sync))))) - -(defun lsp--set-sync-method () - (let* ((sync (gethash "textDocumentSync" (lsp--server-capabilities))) - (kind (if (hash-table-p sync) (gethash "change" sync) sync)) - (method (alist-get kind lsp--sync-methods))) - (setq lsp--server-sync-method (or lsp-document-sync-method - method)))) - -(defun lsp--workspace-apply-edit-handler (_workspace params) - (lsp--apply-workspace-edit (gethash "edit" params))) - -(defun lsp--make-sentinel (workspace) - (cl-check-type workspace lsp--workspace) - (lambda (process exit-str) - (let ((status (process-status process))) - (when (memq status '(exit signal)) - ;; Server has exited. Uninitialize all buffer-local state for this - ;; workspace. - (message "%s: %s has exited (%s)" - (lsp--workspace-root workspace) - (process-name (lsp--workspace-proc workspace)) - (string-trim-right exit-str)) - (dolist (buf (lsp--workspace-buffers workspace)) - (with-current-buffer buf - (lsp--uninitialize-workspace))) - ;; Kill standard error buffer only if the process exited normally. - ;; Leave it intact otherwise for debugging purposes. - (when (and (eq status 'exit) (zerop (process-exit-status process))) - ;; FIXME: The client structure should store the standard error - ;; buffer, not its name. - ;; FIXME: Probably the standard error buffer should be per workspace, - ;; not per client. - (let ((stderr (get-buffer (lsp--client-stderr - (lsp--workspace-client workspace))))) - (when (buffer-live-p stderr) - (kill-buffer stderr)))))))) - -(defun lsp--should-start-p (root) - "Consult `lsp-project-blacklist' and `lsp-project-whitelist' to -determine if a server should be started for the given ROOT -directory." - (or - (cl-some (lambda (p) (string-match-p p root)) - lsp-project-whitelist) - (cl-notany (lambda (p) (string-match-p p root)) - lsp-project-blacklist))) - -(defun lsp--start (client &optional extra-init-params) - (when lsp--cur-workspace - (user-error "LSP mode is already enabled for this buffer")) - (cl-assert client) - (let* ((root (file-truename (funcall (lsp--client-get-root client)))) - (workspace (gethash root lsp--workspaces)) - new-conn response init-params - parser proc cmd-proc) - (if workspace - (progn - (setq lsp--cur-workspace workspace) - (lsp-mode 1)) - - (setf - parser (make-lsp--parser) - lsp--cur-workspace (make-lsp--workspace - :parser parser - :file-versions (make-hash-table :test 'equal) - :root root - :client client) - (lsp--parser-workspace parser) lsp--cur-workspace - new-conn (funcall - (lsp--client-new-connection client) - (lsp--parser-make-filter parser (lsp--client-ignore-regexps client)) - (lsp--make-sentinel lsp--cur-workspace)) - ;; the command line process invoked - cmd-proc (if (consp new-conn) (car new-conn) new-conn) - ;; the process we actually communicate with - proc (if (consp new-conn) (cdr new-conn) new-conn) - - (lsp--workspace-proc lsp--cur-workspace) proc - (lsp--workspace-cmd-proc lsp--cur-workspace) cmd-proc) - - (puthash root lsp--cur-workspace lsp--workspaces) - (lsp-mode 1) - (run-hooks 'lsp-before-initialize-hook) - (setq init-params - `(:processId ,(emacs-pid) - :rootPath ,root - :rootUri ,(lsp--path-to-uri root) - :capabilities ,(lsp--client-capabilities) - :initializationOptions ,(if (functionp extra-init-params) - (funcall extra-init-params lsp--cur-workspace) - extra-init-params))) - (setf response (lsp--send-request - (lsp--make-request "initialize" init-params))) - (unless response - (signal 'lsp-empty-response-error (list "initialize"))) - (setf (lsp--workspace-server-capabilities lsp--cur-workspace) - (gethash "capabilities" response)) - ;; Version 3.0 now sends an "initialized" notification to allow registration - ;; of server capabilities - (lsp--send-notification (lsp--make-notification "initialized" (make-hash-table))) - (run-hooks 'lsp-after-initialize-hook)) - (lsp--text-document-did-open))) - -(defun lsp--text-document-did-open () - (run-hooks 'lsp-before-open-hook) - (puthash (current-buffer) 0 (lsp--workspace-file-versions lsp--cur-workspace)) - (push (current-buffer) (lsp--workspace-buffers lsp--cur-workspace)) - (lsp--send-notification (lsp--make-notification - "textDocument/didOpen" - `(:textDocument ,(lsp--make-text-document-item)))) - - (add-hook 'after-save-hook #'lsp-on-save nil t) - (add-hook 'kill-buffer-hook #'lsp--text-document-did-close nil t) - - (when lsp-enable-eldoc - ;; XXX: The documentation for `eldoc-documentation-function' suggests - ;; using `add-function' for modifying its value, use that instead? - (setq-local eldoc-documentation-function #'lsp--on-hover) - (eldoc-mode 1)) - - (when (and lsp-enable-indentation - (lsp--capability "documentRangeFormattingProvider")) - (setq-local indent-region-function #'lsp-format-region)) - - (when (and lsp-enable-xref - (lsp--capability "referencesProvider") - (lsp--capability "definitionProvider")) - (setq-local xref-backend-functions (list #'lsp--xref-backend))) - - (when (and lsp-enable-completion-at-point (lsp--capability "completionProvider")) - (setq-local completion-at-point-functions nil) - (add-hook 'completion-at-point-functions #'lsp-completion-at-point nil t)) - - ;; Make sure the hook is local (last param) otherwise we see all changes for all buffers - (add-hook 'before-change-functions #'lsp-before-change nil t) - (add-hook 'after-change-functions #'lsp-on-change nil t) - (add-hook 'after-revert-hook #'lsp-on-revert nil t) - (add-hook 'before-save-hook #'lsp--before-save nil t) - (add-hook 'auto-save-hook #'lsp--on-auto-save nil t) - (lsp--set-sync-method) - (run-hooks 'lsp-after-open-hook)) - -(define-inline lsp--text-document-identifier () - "Make TextDocumentIdentifier. - -interface TextDocumentIdentifier { - uri: string; -}" - (inline-quote (list :uri (lsp--buffer-uri)))) - -(define-inline lsp--versioned-text-document-identifier () - "Make VersionedTextDocumentIdentifier. - -interface VersionedTextDocumentIdentifier extends TextDocumentIdentifier { - version: number; -}" - (inline-quote (plist-put (lsp--text-document-identifier) - :version (lsp--cur-file-version)))) - -(define-inline lsp--position (line char) - "Make a Position object for the given LINE and CHAR. - -interface Position { - line: number; - character: number; -}" - (inline-quote (list :line ,line :character ,char))) - -(define-inline lsp--cur-line () - (inline-quote (1- (line-number-at-pos)))) - -(define-inline lsp--cur-column () - (inline-quote (- (point) (line-beginning-position)))) - -(define-inline lsp--cur-position () - "Make a Position object for the current point." - (inline-quote - (save-restriction - (widen) - (lsp--position (lsp--cur-line) (lsp--cur-column))))) - -(defun lsp--point-to-position (point) - "Convert POINT to Position." - (save-excursion - (goto-char point) - (lsp--cur-position))) - -(define-inline lsp--position-p (p) - (inline-quote - (and (numberp (plist-get ,p :line)) (numberp (plist-get ,p :character))))) - -(define-inline lsp--range (start end) - "Make Range body from START and END. - -interface Range { - start: Position; - end: Position; - }" - ;; make sure start and end are Position objects - (inline-quote - (progn - (cl-check-type ,start (satisfies lsp--position-p)) - (cl-check-type ,end (satisfies lsp--position-p)) - (list :start ,start :end ,end)))) - -(define-inline lsp--region-to-range (start end) - "Make Range object for the current region." - (inline-quote (lsp--range (lsp--point-to-position ,start) - (lsp--point-to-position ,end)))) - -(defun lsp--current-region-or-pos () - "If the region is active return that, else get the point." - (if (use-region-p) - (lsp--region-to-range (region-beginning) (region-end)) - (lsp--region-to-range (point) (point)))) - -(defun lsp--get-start-position () - "Get the start of the region if active, else current point." - (let ((pos (if (use-region-p) - (region-beginning) - (point)))) - (lsp-point-to-position pos))) - -(defun lsp--get-end-position () - "Get the end of the region if active, else current point." - (let ((pos (if (use-region-p) - (region-end) - (point)))) - (lsp-point-to-position pos))) - -(define-inline lsp--range-start-line (range) - "Return the start line for a given LSP range, in LSP coordinates" - (inline-quote (plist-get (plist-get ,range :start) :line))) - -(define-inline lsp--range-end-line (range) - "Return the end line for a given LSP range, in LSP coordinates" - (inline-quote (plist-get (plist-get ,range :end) :line))) - -(defun lsp--apply-workspace-edit (edit) - "Apply the WorkspaceEdit object EDIT. - -interface WorkspaceEdit { - changes?: { [uri: string]: TextEdit[]; }; - documentChanges?: TextDocumentEdit[]; -}" - (let ((changes (gethash "changes" edit)) - (document-changes (gethash "documentChanges" edit))) - (if document-changes - (seq-do #'lsp--apply-text-document-edit document-changes) - - (when (hash-table-p changes) - (maphash - (lambda (uri text-edits) - (let ((filename (lsp--uri-to-path uri))) - (with-current-buffer (find-file-noselect filename) - (lsp--apply-text-edits text-edits)))) - changes))))) - -(defun lsp--apply-text-document-edit (edit) - "Apply the TextDocumentEdit object EDIT. -If the file is not being visited by any buffer, it is opened with -`find-file-noselect'. -Because lsp-mode does not store previous document versions, the edit is only -applied if the version of the textDocument matches the version of the -corresponding file. - -interface TextDocumentEdit { - textDocument: VersionedTextDocumentIdentifier; - edits: TextEdit[]; -}" - (let* ((ident (gethash "textDocument" edit)) - (filename (lsp--uri-to-path (gethash "uri" ident))) - (version (gethash "version" ident))) - (with-current-buffer (find-file-noselect filename) - (when (and version (= version (lsp--cur-file-version))) - (lsp--apply-text-edits (gethash "edits" edit)))))) - -(defun lsp--text-edit-sort-predicate (e1 e2) - (let ((start1 (lsp--position-to-point (gethash "start" (gethash "range" e1)))) - (start2 (lsp--position-to-point (gethash "start" (gethash "range" e2))))) - (if (= start1 start2) - (let ((end1 (lsp--position-to-point (gethash "end" (gethash "range" e1)))) - (end2 (lsp--position-to-point (gethash "end" (gethash "range" e2))))) - (> end1 end2)) - - (> start1 start2)))) - -(define-inline lsp--apply-text-edits (edits) - "Apply the edits described in the TextEdit[] object in EDITS." - (inline-quote - ;; We sort text edits so as to apply edits that modify earlier parts of the - ;; document first. Furthermore, because the LSP spec dictates that: - ;; "If multiple inserts have the same position, the order in the array - ;; defines which edit to apply first." - ;; We reverse the initial list to make sure that the order among edits with - ;; the same position is preserved. - - (seq-do #'lsp--apply-text-edit (sort (nreverse ,edits) #'lsp--text-edit-sort-predicate)))) - -(defun lsp--apply-text-edit (text-edit) - "Apply the edits described in the TextEdit object in TEXT-EDIT." - (let* ((range (gethash "range" text-edit)) - (start-point (lsp--position-to-point (gethash "start" range))) - (end-point (lsp--position-to-point (gethash "end" range)))) - (save-excursion - (goto-char start-point) - (delete-region start-point end-point) - (insert (gethash "newText" text-edit))))) - -(define-inline lsp--capability (cap &optional capabilities) - "Get the value of capability CAP. If CAPABILITIES is non-nil, use them instead." - (inline-quote (gethash ,cap (or ,capabilities (lsp--server-capabilities) (make-hash-table))))) - -(define-inline lsp--registered-capability (method) - (inline-quote - (seq-find (lambda (reg) (equal (lsp--registered-capability-method reg) ,method)) - (lsp--workspace-registered-server-capabilities lsp--cur-workspace) - nil))) - -(define-inline lsp--registered-capability-by-id (id) - (inline-quote - (seq-find (lambda (reg) (equal (lsp--registered-capability-id reg) ,id)) - (lsp--workspace-registered-server-capabilities lsp--cur-workspace) - nil))) - -(defvar-local lsp--before-change-vals nil - "Store the positions from the `lsp-before-change' function - call, for validation and use in the `lsp-on-change' function.") - -(defun lsp--text-document-content-change-event (start end length) - "Make a TextDocumentContentChangeEvent body for START to END, of length LENGTH." - ;; So (47 54 0) means add 7 chars starting at pos 47 - ;; must become - ;; {"range":{"start":{"line":5,"character":6} - ;; ,"end" :{"line":5,"character":6}} - ;; ,"rangeLength":0 - ;; ,"text":"\nbb = 5"} - ;; - ;; And (47 47 7) means delete 7 chars starting at pos 47 - ;; must become - ;; {"range":{"start":{"line":6,"character":0} - ;; ,"end" :{"line":7,"character":0}} - ;; ,"rangeLength":7 - ;; ,"text":""} - ;; - ;; (208 221 3) means delete 3 chars starting at pos 208, and replace them with - ;; 13 chars. So it must become - ;; {"range":{"start":{"line":5,"character":8} - ;; ,"end" :{"line":5,"character":11}} - ;; ,"rangeLength":3 - ;; ,"text":"new-chars-xxx"} - ;; - - ;; Adding text: - ;; lsp-before-change:(start,end)=(33,33) - ;; lsp-on-change:(start,end,length)=(33,34,0) - ;; - ;; Changing text: - ;; lsp-before-change:(start,end)=(208,211) - ;; lsp-on-change:(start,end,length)=(208,221,3) - ;; - ;; Deleting text: - ;; lsp-before-change:(start,end)=(19,27) - ;; lsp-on-change:(start,end,length)=(19,19,8) - - (if (eq length 0) - ;; Adding something only, work from start only - `(:range ,(lsp--range (lsp--point-to-position start) - (lsp--point-to-position start)) - :rangeLength 0 - :text ,(buffer-substring-no-properties start end)) - - (if (eq start end) - ;; Deleting something only - (if (lsp--bracketed-change-p start end length) - ;; The before-change value is bracketed, use it - `(:range ,(lsp--range (lsp--point-to-position start) - (plist-get lsp--before-change-vals :end-pos)) - :rangeLength ,length - :text "") - ;; If the change is not bracketed, send a full change event instead. - (lsp--full-change-event)) - - ;; Deleting some things, adding others - (if (lsp--bracketed-change-p start end length) - ;; The before-change value is valid, use it - `(:range ,(lsp--range (lsp--point-to-position start) - (plist-get lsp--before-change-vals :end-pos)) - :rangeLength ,length - :text ,(buffer-substring-no-properties start end)) - (lsp--full-change-event))))) - - -;; TODO: Add tests for this function. -(defun lsp--bracketed-change-p (start _end length) - "If the before and after positions are the same, and the length -is the size of the start range, we are probably good." - (and (eq start (plist-get lsp--before-change-vals :start) ) - (eq length (- (plist-get lsp--before-change-vals :end) - (plist-get lsp--before-change-vals :start))))) - -;; Observed from vscode for applying a diff replacing one line with -;; another. Emacs on-change shows this as a delete followed by an -;; add. - -;; 2017-04-22 17:43:59 [ThreadId 11] DEBUG haskell-lsp - ---> {"jsonrpc":"2.0","method":"textDocument/didChange","params": -;; {"textDocument":{"uri":"file:///home/alanz/tmp/haskell-hie-test-project/src/Foo.hs","version":2} -;; ,"contentChanges":[{"range":{"start":{"line":7,"character":0} -;; ,"end": {"line":7,"character":8}} -;; ,"rangeLength":8 -;; ,"text":"baz ="}]}} - - -(defun lsp--full-change-event () - (save-restriction - (widen) - `(:text ,(buffer-substring-no-properties (point-min) (point-max))))) - -(defun lsp-before-change (start end) - "Executed before a file is changed. -Added to `before-change-functions'." - ;; Note: - ;; - ;; This variable holds a list of functions to call when Emacs is about to - ;; modify a buffer. Each function gets two arguments, the beginning and end of - ;; the region that is about to change, represented as integers. The buffer - ;; that is about to change is always the current buffer when the function is - ;; called. - ;; - ;; WARNING: - ;; - ;; Do not expect the before-change hooks and the after-change hooks be called - ;; in balanced pairs around each buffer change. Also don't expect the - ;; before-change hooks to be called for every chunk of text Emacs is about to - ;; delete. These hooks are provided on the assumption that Lisp programs will - ;; use either before- or the after-change hooks, but not both, and the - ;; boundaries of the region where the changes happen might include more than - ;; just the actual changed text, or even lump together several changes done - ;; piecemeal. - ;; (message "lsp-before-change:(start,end)=(%s,%s)" start end) - (with-demoted-errors "Error in ‘lsp-before-change’: %S" - (setq lsp--before-change-vals - (list :start start - :end end - :start-pos (lsp--point-to-position start) - :end-pos (lsp--point-to-position end))))) - -(defun lsp-on-change (start end length) - "Executed when a file is changed. -Added to `after-change-functions'." - ;; Note: - ;; - ;; Each function receives three arguments: the beginning and end of the region - ;; just changed, and the length of the text that existed before the change. - ;; All three arguments are integers. The buffer that has been changed is - ;; always the current buffer when the function is called. - ;; - ;; The length of the old text is the difference between the buffer positions - ;; before and after that text as it was before the change. As for the - ;; changed text, its length is simply the difference between the first two - ;; arguments. - ;; - ;; So (47 54 0) means add 7 chars starting at pos 47 - ;; So (47 47 7) means delete 7 chars starting at pos 47 - ;; (message "lsp-on-change:(start,end,length)=(%s,%s,%s)" start end length) - ;; (message "lsp-on-change:(lsp--before-change-vals)=%s" lsp--before-change-vals) - (with-demoted-errors "Error in ‘lsp-on-change’: %S" - (save-match-data - ;; A (revert-buffer) call with the 'preserve-modes parameter (eg, as done - ;; by auto-revert-mode) will cause this hander to get called with a nil - ;; buffer-file-name. We need the buffer-file-name to send notifications; - ;; so we skip handling revert-buffer-caused changes and instead handle - ;; reverts separately in lsp-on-revert - (when (and lsp--cur-workspace (not revert-buffer-in-progress-p)) - (lsp--inc-cur-file-version) - (unless (eq lsp--server-sync-method 'none) - (lsp--send-notification - (lsp--make-notification - "textDocument/didChange" - `(:textDocument - ,(lsp--versioned-text-document-identifier) - :contentChanges - ,(pcase lsp--server-sync-method - ('incremental (vector (lsp--text-document-content-change-event - start end length))) - ('full (vector (lsp--full-change-event)))))))))))) - -(defun lsp-on-revert () - "Executed when a file is reverted. -Added to `after-revert-hook'." - (let ((n (buffer-size)) - (revert-buffer-in-progress-p nil)) - (lsp-on-change 0 n n))) - -(defun lsp--text-document-did-close () - "Executed when the file is closed, added to `kill-buffer-hook'." - (when lsp--cur-workspace - (with-demoted-errors "Error on ‘lsp--text-document-did-close’: %S" - (let ((file-versions (lsp--workspace-file-versions lsp--cur-workspace)) - (old-buffers (lsp--workspace-buffers lsp--cur-workspace))) - ;; remove buffer from the current workspace's list of buffers - ;; do a sanity check first - (when (memq (current-buffer) old-buffers) - (setf (lsp--workspace-buffers lsp--cur-workspace) - (delq (current-buffer) old-buffers)) - - (remhash (current-buffer) file-versions) - (with-demoted-errors "Error sending didClose notification in ‘lsp--text-document-did-close’: %S" - (lsp--send-notification - (lsp--make-notification - "textDocument/didClose" - `(:textDocument ,(lsp--versioned-text-document-identifier))))) - (when (= 0 (hash-table-count file-versions)) - (lsp--shutdown-cur-workspace))))))) - -(define-inline lsp--will-save-text-document-params (reason) - (cl-check-type reason number) - (inline-quote - (list :textDocument (lsp--text-document-identifier) - :reason ,reason))) - -(defun lsp--before-save () - (when lsp--cur-workspace - (with-demoted-errors "Error in ‘lsp--before-save’: %S" - (let ((params (lsp--will-save-text-document-params 1))) - (when (lsp--send-will-save-p) - (lsp--send-notification - (lsp--make-notification "textDocument/willSave" params))) - (when (and (lsp--send-will-save-wait-until-p) lsp-before-save-edits) - (lsp--apply-text-edits - (lsp--send-request (lsp--make-request - "textDocument/willSaveWaitUntil" params)))))))) - -(defun lsp--on-auto-save () - (when (and lsp--cur-workspace - (lsp--send-will-save-p)) - (with-demoted-errors "Error in ‘lsp--on-auto-save’: %S" - (lsp--send-notification - (lsp--make-notification - "textDocument/willSave" (lsp--will-save-text-document-params 2)))))) - -(defun lsp--text-document-did-save () - "Executed when the file is closed, added to `after-save-hook''." - (when lsp--cur-workspace - (with-demoted-errors "Error on ‘lsp--text-document-did-save: %S’" - (lsp--send-notification - (lsp--make-notification - "textDocument/didSave" - `(:textDocument ,(lsp--versioned-text-document-identifier) - :text ,(if (lsp--save-include-text-p) - (save-excursion - (widen) - (buffer-substring-no-properties (point-min) (point-max))) - nil))))))) - -(define-inline lsp--text-document-position-params (&optional identifier position) - "Make TextDocumentPositionParams for the current point in the current document. -If IDENTIFIER and POSITION are non-nil, they will be used as the document identifier -and the position respectively." - (inline-quote (list :textDocument (or ,identifier (lsp--text-document-identifier)) - :position (or ,position (lsp--cur-position))))) - -(define-inline lsp--text-document-code-action-params () - "Make CodeActionParams for the current region in the current document." - (inline-quote (list :textDocument (lsp--text-document-identifier) - :range (lsp--current-region-or-pos) - :context (list :diagnostics (lsp--cur-line-diagnotics))))) - -(defun lsp--cur-line-diagnotics () - "Return any diagnostics that apply to the current line." - (let* ((diags (gethash buffer-file-name lsp--diagnostics nil)) - (range (lsp--current-region-or-pos)) - (start-line (lsp--range-start-line range)) - (end-line (lsp--range-end-line range)) - (diags-in-range (cl-remove-if-not - (lambda (diag) - (let ((line (lsp-diagnostic-line diag))) - (and (>= line start-line) (<= line end-line)))) - diags))) - (cl-coerce (seq-map #'lsp-diagnostic-original diags-in-range) 'vector))) - -(defconst lsp--completion-item-kind - [nil - "Text" - "Method" - "Function" - "Constructor" - "Field" - "Variable" - "Class" - "Interface" - "Module" - "Property" - "Unit" - "Value" - "Enum" - "Keyword" - "Snippet" - "Color" - "File" - "Reference" - "Folder" - "EnumMember" - "Constant" - "Struct" - "Event" - "Operator" - "TypeParameter" - ]) - -(defun lsp--gethash (key table &optional dflt) - "Look up KEY in TABLE and return its associated value, -unless KEY not found or its value is falsy, when it returns DFLT. -DFLT defaults to nil. - -Needed for completion request fallback behavior for the fields -'sortText', 'filterText', and 'insertText' as described here: - -https://microsoft.github.io/language-server-protocol/specification#textDocument_completion" - - (let ((result (gethash key table dflt))) - (when (member result '(nil "" 0 :json-false)) - (setq result dflt)) - result)) - -(defun lsp--make-completion-item (item) - (propertize (lsp--gethash "insertText" item (gethash "label" item "")) - 'lsp-completion-item - item)) - -(defun lsp--annotate (item) - (let* ((table (plist-get (text-properties-at 0 item) 'lsp-completion-item)) - (detail (gethash "detail" table nil)) - (kind-index (gethash "kind" table nil))) - ;; We need check index before call `aref'. - (when kind-index - (setq kind (aref lsp--completion-item-kind kind-index)) - (concat - " " - detail - (when kind (format " (%s)" kind)))) - )) - -(defun lsp--sort-string (c) - (lsp--gethash "sortText" c (gethash "label" c ""))) - -(defun lsp--sort-completions (completions) - (sort completions (lambda (c1 c2) - (string-lessp - (lsp--sort-string c1) - (lsp--sort-string c2))))) - -(defun lsp--default-prefix-function () - (bounds-of-thing-at-point 'symbol)) - -(defun lsp--get-completions () - (with-demoted-errors "Error in ‘lsp--get-completions’: %S" - (let* ((prefix-function (or (lsp--client-prefix-function - (lsp--workspace-client lsp--cur-workspace)) - #'lsp--default-prefix-function)) - (bounds (funcall prefix-function))) - (list - (if bounds (car bounds) (point)) - (if bounds (cdr bounds) (point)) - (completion-table-dynamic - #'(lambda (_) - ;; *we* don't need to know the string being completed - ;; the language server does all the work by itself - (let* ((resp (lsp--send-request - (lsp--make-request - "textDocument/completion" - (lsp--text-document-position-params)))) - (items (cond - ((null resp) nil) - ((hash-table-p resp) (gethash "items" resp nil)) - ((sequencep resp) resp)))) - (seq-map #'lsp--make-completion-item items)))) - :annotation-function #'lsp--annotate - :display-sort-function #'lsp--sort-completions)))) - -(defun lsp--resolve-completion (item) - (lsp--cur-workspace-check) - (cl-assert item nil "Completion item must not be nil") - (if (gethash "resolveProvider" (lsp--capability "completionProvider")) - (lsp--send-request - (lsp--make-request - "completionItem/resolve" - item)) - item)) - -(defun lsp--extract-line-from-buffer (pos) - "Return the line pointed to by POS (a Position object) in the current buffer." - (let* ((point (lsp--position-to-point pos)) - (inhibit-field-text-motion t)) - (save-excursion - (goto-char point) - (buffer-substring-no-properties (line-beginning-position) - (line-end-position))))) - -(defun lsp--xref-make-item (filename location) - "Return a xref-item from a LOCATION in FILENAME." - (let* ((range (gethash "range" location)) - (pos-start (gethash "start" range)) - (pos-end (gethash "end" range)) - (line (lsp--extract-line-from-buffer pos-start)) - (start (gethash "character" pos-start)) - (end (gethash "character" pos-end)) - (len (length line))) - (add-face-text-property (max (min start len) 0) - (max (min end len) 0) - 'highlight t line) - ;; LINE is nil when FILENAME is not being current visited by any buffer. - (xref-make (or line filename) - (xref-make-file-location filename - (1+ (gethash "line" pos-start)) - (gethash "character" pos-start))))) - -(defun lsp--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 `xref-item'." - (let* ((filename (car file)) - (visiting (find-buffer-visiting filename)) - (fn (lambda (loc) (lsp--xref-make-item filename loc)))) - (if visiting - (with-current-buffer visiting - (mapcar fn (cdr file))) - (when (file-readable-p filename) - (with-temp-buffer - (insert-file-contents-literally filename) - (mapcar fn (cdr file))))))) - -(defun lsp--locations-to-xref-items (locations) - "Return a list of `xref-item' from LOCATIONS. -LOCATIONS is an array of Location objects: - -interface Location { - uri: DocumentUri; - range: Range; -}" - (when locations - (let* ((fn (lambda (loc) (lsp--uri-to-path (gethash "uri" loc)))) - ;; locations-by-file is an alist of the form - ;; ((FILENAME . LOCATIONS)...), where FILENAME is a string of the - ;; actual file name, and LOCATIONS is a list of Location objects - ;; pointing to Ranges inside that file. - (locations-by-file (seq-group-by fn locations)) - ;; items-by-file is a list of list of xref-item - (items-by-file (mapcar #'lsp--get-xrefs-in-file locations-by-file))) - ;; flatten the list - (apply #'append items-by-file)))) - -(defun lsp--get-definitions () - "Get definition of the current symbol under point. -Returns xref-item(s)." - (let ((defs (lsp--send-request (lsp--make-request - "textDocument/definition" - (lsp--text-document-position-params))))) - ;; textDocument/definition returns Location | Location[] - (lsp--locations-to-xref-items (if (listp defs) defs (list defs))))) - -(defun lsp--make-reference-params (&optional td-position include-declaration) - "Make a ReferenceParam object. -If TD-POSITION is non-nil, use it as TextDocumentPositionParams object instead. -If INCLUDE-DECLARATION is non-nil, request the server to include declarations." - (let ((json-false :json-false)) - (plist-put (or td-position (lsp--text-document-position-params)) - :context `(:includeDeclaration ,(or include-declaration json-false))))) - -(defun lsp--get-references () - "Get all references for the symbol under point. -Returns xref-item(s)." - (let ((refs (lsp--send-request (lsp--make-request - "textDocument/references" - (lsp--make-reference-params))))) - (lsp--locations-to-xref-items refs))) - -(defun lsp--cancel-request (id) - (lsp--cur-workspace-check) - (cl-check-type id (or number string)) - (let ((response-handlers (lsp--client-response-handlers (lsp--workspace-client - lsp--cur-workspace)))) - (remhash id response-handlers) - (lsp--send-notification (lsp--make-notification "$/cancelRequest" - `(:id ,id))))) - -(defun lsp--on-hover () - ;; This function is used as ‘eldoc-documentation-function’, so it’s important - ;; that it doesn’t fail. - (with-demoted-errors "Error in ‘lsp--on-hover’: %S" - (when (and (lsp--capability "documentHighlightProvider") - lsp-highlight-symbol-at-point) - (lsp-symbol-highlight)) - (when (and (or (lsp--capability "codeActionProvider") - (lsp--registered-capability "textDocument/codeAction")) - lsp-enable-codeaction) - (lsp--text-document-code-action)) - (when (and (lsp--capability "hoverProvider") lsp-enable-eldoc) - (funcall lsp-hover-text-function)))) - -(defun lsp-describe-thing-at-point () - "Display the full documentation of the thing at point." - (interactive) - (lsp--cur-workspace-check) - (let* ((client (lsp--workspace-client lsp--cur-workspace)) - (contents (gethash "contents" (lsp--send-request - (lsp--make-request "textDocument/hover" - (lsp--text-document-position-params)))))) - (pop-to-buffer - (with-current-buffer (get-buffer-create "*lsp-help*") - (let ((inhibit-read-only t)) - (erase-buffer) - (insert (lsp--render-on-hover-content contents client t)) - (goto-char (point-min)) - (view-mode t) - (current-buffer)))))) - -(defvar-local lsp--cur-hover-request-id nil) - -(defun lsp--text-document-hover-string () - "interface Hover { - contents: MarkedString | MarkedString[]; - range?: Range; -} - -type MarkedString = string | { language: string; value: string };" - (lsp--cur-workspace-check) - (when lsp--cur-hover-request-id - (lsp--cancel-request lsp--cur-hover-request-id)) - (let* ((client (lsp--workspace-client lsp--cur-workspace)) - bounds body) - (when (symbol-at-point) - (setq bounds (bounds-of-thing-at-point 'symbol) - body (lsp--send-request-async (lsp--make-request "textDocument/hover" - (lsp--text-document-position-params)) - (lsp--make-hover-callback client (car bounds) (cdr bounds) - (current-buffer))) - lsp--cur-hover-request-id (plist-get body :id)) - (cl-assert (integerp lsp--cur-hover-request-id))))) - -(defun lsp--render-markup-content-1 (kind content) - (if (functionp lsp-render-markdown-markup-content) - (let ((out (funcall lsp-render-markdown-markup-content kind content))) - (cl-assert (stringp out) t - "value returned by lsp-render-markdown-markup-content should be a string") - out) - content)) - -(defun lsp--render-markup-content (content) - "Render MarkupContent object CONTENT. - -export interface MarkupContent { - kind: MarkupKind; - value: string; -}" - (let ((kind (gethash "kind" content)) - (content (gethash "value" content))) - (lsp--render-markup-content-1 kind content))) - -(define-inline lsp--point-is-within-bounds-p (start end) - "Return whether the current point is within START and END." - (inline-quote - (let ((p (point))) - (and (>= p ,start) (<= p ,end))))) - -(define-inline lsp--markup-content-p (obj) - (inline-letevals (obj) - (inline-quote (and (hash-table-p ,obj) - (gethash "kind" ,obj nil) (gethash "value" ,obj nil))))) - -(defun lsp--render-on-hover-content (contents client render-all) - "Render the content received from 'document/onHover' request. - -CLIENT - client to use. -CONTENTS - MarkedString | MarkedString[] | MarkupContent -RENDER-ALL if set to nil render only the first element from CONTENTS." - (let ((renderers (lsp--client-string-renderers client)) - (default-client-renderer (lsp--client-default-renderer client))) - (string-join - (seq-map - (lambda (e) - (let (renderer) - (cond - ;; hash table, language renderer set - ((and (hash-table-p e) - (setq renderer - (if-let (language (gethash "language" e)) - (cdr (assoc-string language renderers)) - default-client-renderer))) - (when (gethash "value" e nil) - (funcall renderer (gethash "value" e)))) - - ;; hash table - workspace renderer not set - ;; trying to render using global renderer - ((lsp--markup-content-p e) (lsp--render-markup-content e)) - - ;; hash table - anything other has failed - ((hash-table-p e) (gethash "value" e nil)) - - ;; string, default workspace renderer set - (default-client-renderer (funcall default-client-renderer e)) - - ;; no rendering - (t e)))) - (if (sequencep contents) - (if render-all - contents - (seq-take contents 1)) - (list contents))) - "\n"))) - -;; start and end are the bounds of the symbol at point -(defun lsp--make-hover-callback (client start end buffer) - (lambda (hover) - (with-current-buffer buffer - (setq lsp--cur-hover-request-id nil)) - (when (and hover - (lsp--point-is-within-bounds-p start end) - (eq (current-buffer) buffer) (eldoc-display-message-p)) - (let ((contents (gethash "contents" hover))) - (when contents - (eldoc-message (lsp--render-on-hover-content contents - client - lsp-eldoc-render-all))))))) - -(defun lsp-provide-marked-string-renderer (client language renderer) - (cl-check-type language string) - (cl-check-type renderer function) - (setf (alist-get language (lsp--client-string-renderers client)) renderer)) - -(defun lsp-provide-default-marked-string-renderer (client renderer) - "Set the RENDERER for CLIENT. - -It will be used when no language has been specified in document/onHover result." - (cl-check-type renderer function) - (setf (lsp--client-default-renderer client) renderer)) - -(defun lsp-info-under-point () - "Show relevant documentation for the thing under point." - (interactive) - (lsp--text-document-hover-string)) - -(defvar-local lsp--current-signature-help-request-id nil) - -(defun lsp--text-document-signature-help () - "interface SignatureHelp { -signatures: SignatureInformation[]; -activeSignature?: number; -activeParameter?: number; -}; - -interface SignatureInformation { -label: string; -documentation?: string | MarkupContent; -parameters?: ParameterInformation[]; -}; - -interface ParameterInformation { -label: string; -documentation?: string | MarkupContent; -}; - -interface MarkupContent { -kind: MarkupKind; -value: string; -}; - -type MarkupKind = 'plaintext' | 'markdown';" - (lsp--cur-workspace-check) - (when lsp--current-signature-help-request-id - (lsp--cancel-request lsp--current-signature-help-request-id)) - (let (bounds body) - (when (symbol-at-point) - (setq bounds (bounds-of-thing-at-point 'symbol) - body (lsp--send-request-async - (lsp--make-request "textDocument/signatureHelp" - (lsp--text-document-position-params)) - (lsp--make-text-document-signature-help-callback - (car bounds) (cdr bounds) (current-buffer))) - lsp--current-signature-help-request-id (plist-get body :id)) - (cl-assert (integerp lsp--current-signature-help-request-id))))) - -(defun lsp--make-text-document-signature-help-callback (start end buffer) - (lambda (signature-help) - (with-current-buffer buffer - (setq lsp--current-signature-help-request-id nil)) - (when (and signature-help - (lsp--point-is-within-bounds-p start end) - (eq (current-buffer) buffer) (eldoc-display-message-p)) - (let* ((active-signature-number - (or (gethash "activeSignature" signature-help) 0)) - (active-signature (nth - active-signature-number - (gethash "signatures" signature-help)))) - (when active-signature - (eldoc-message (gethash "label" active-signature))))))) - -;; NOTE: the code actions cannot currently be applied. There is some non-GNU -;; code to do this in the lsp-haskell module. We still need a GNU version, here. -;; PRs accepted. -(defvar-local lsp-code-actions nil - "Code actions for the buffer.") - -(defvar-local lsp-code-action-params nil - "The last code action params.") - -(defun lsp--text-document-code-action () - "Request code action to automatically fix issues reported by -the diagnostics." - (lsp--cur-workspace-check) - (unless (or (lsp--capability "codeActionProvider") - (lsp--registered-capability "textDocument/codeAction")) - (signal 'lsp-capability-not-supported (list "codeActionProvider"))) - (let ((params (lsp--text-document-code-action-params))) - (lsp--send-request-async - (lsp--make-request "textDocument/codeAction" params) - (lambda (actions) - (lsp--set-code-action-params (current-buffer) actions params))))) - -(defun lsp--command-get-title (cmd) - "Given a Command object CMD, get the title. -If title is nil, return the name for the command handler." - (gethash "title" cmd (gethash "command" cmd))) - -(defun lsp--set-code-action-params (buf actions params) - "Update set `lsp-code-actions' to ACTIONS and `lsp-code-action-params' to PARAMS in BUF." - (when (buffer-live-p buf) - (with-current-buffer buf - (when (equal params (lsp--text-document-code-action-params)) - (setq lsp-code-actions actions) - (setq lsp-code-action-params params))))) - -(defun lsp--command-p (cmd) - (and (cl-typep cmd 'hash-table) - (cl-typep (gethash "title" cmd) 'string) - (cl-typep (gethash "command" cmd) 'string))) - -(defun lsp--select-action (actions) - "Select an action to execute from ACTIONS." - (if actions - (let ((name->action (mapcar (lambda (a) - (list (lsp--command-get-title a) a)) - actions))) - (cadr (assoc - (completing-read "Select code action: " name->action) - name->action))) - (error "No actions to select from"))) - -(defun lsp-get-or-calculate-code-actions () - "Get or calculate the current code actions. - -The method will either retrieve the current code actions or it will calculate the actual one." - (let ((current-code-action-params (lsp--text-document-code-action-params))) - (when (not (equal current-code-action-params lsp-code-action-params)) - (let* ((request-params (lsp--make-request - "textDocument/codeAction" - (lsp--text-document-code-action-params))) - (actions (lsp--send-request request-params))) - (setq lsp-code-action-params current-code-action-params) - (lsp--set-code-action-params (current-buffer) - actions - current-code-action-params))) - lsp-code-actions)) - -(defun lsp-execute-code-action (action) - "Execute code action ACTION. - -If ACTION is not set it will be selected from `lsp-code-actions'." - (interactive (list - (lsp--select-action (lsp-get-or-calculate-code-actions)))) - (lsp--cur-workspace-check) - (let* ((command (gethash "command" action)) - (action-handler (gethash command - (lsp--client-action-handlers - (lsp--workspace-client lsp--cur-workspace))))) - (if action-handler - (funcall action-handler action) - (lsp--execute-command action)))) - -(defvar-local lsp-code-lenses nil - "A list of code lenses computed for the buffer.") - -(defun lsp--update-code-lenses (&optional callback) - "Update the list of code lenses for the current buffer. -Optionally, CALLBACK is a function that accepts a single argument, the code lens object." - (lsp--cur-workspace-check) - (when callback - (cl-check-type callback function)) - (when (gethash "codeLensProvider" (lsp--server-capabilities)) - (lsp--send-request-async (lsp--make-request "textDocument/codeLens" - `(:textDocument ,(lsp--text-document-identifier))) - (let ((buf (current-buffer))) - #'(lambda (lenses) - (with-current-buffer buf - (setq lsp-code-lenses lenses) - (when callback - (funcall callback lenses)))))))) - -(defun lsp--make-document-formatting-options () - (let ((json-false :json-false)) - `(:tabSize ,tab-width :insertSpaces - ,(if indent-tabs-mode json-false t)))) - -(defun lsp--make-document-formatting-params () - `(:textDocument ,(lsp--text-document-identifier) - :options ,(lsp--make-document-formatting-options))) - -(defun lsp-format-buffer () - "Ask the server to format this document." - (interactive "*") - (unless (or (lsp--capability "documentFormattingProvider") - (lsp--registered-capability "textDocument/formatting")) - (signal 'lsp-capability-not-supported (list "documentFormattingProvider"))) - (let ((edits (lsp--send-request (lsp--make-request - "textDocument/formatting" - (lsp--make-document-formatting-params))))) - (if (fboundp 'replace-buffer-contents) - (let ((current-buffer (current-buffer))) - (with-temp-buffer - (insert-buffer-substring-no-properties current-buffer) - (lsp--apply-text-edits edits) - (let ((temp-buffer (current-buffer))) - (with-current-buffer current-buffer - (replace-buffer-contents temp-buffer))))) - (let ((point (point)) - (w-start (window-start))) - (lsp--apply-text-edits edits) - (goto-char point) - (goto-char (line-beginning-position)) - (set-window-start (selected-window) w-start))))) - -(defun lsp--make-document-range-formatting-params (start end) - "Make DocumentRangeFormattingParams for selected region. -interface DocumentRangeFormattingParams { - textDocument: TextDocumentIdentifier; - range: Range; - options: FormattingOptions; -}" - (plist-put (lsp--make-document-formatting-params) - :range (lsp--region-to-range start end))) - -(defconst lsp--highlight-kind-face - '((1 . lsp-face-highlight-textual) - (2 . lsp-face-highlight-read) - (3 . lsp-face-highlight-write))) - -(defun lsp--remove-cur-overlays () - (let ((overlays (lsp--workspace-highlight-overlays lsp--cur-workspace)) - (buf (current-buffer))) - (dolist (overlay (gethash buf overlays)) - (delete-overlay overlay)) - (remhash buf overlays))) - -(defun lsp-symbol-highlight () - "Highlight all relevant references to the symbol under point." - (interactive) - (lsp--send-request-async (lsp--make-request "textDocument/documentHighlight" - (lsp--text-document-position-params)) - (lsp--make-symbol-highlight-callback (current-buffer)))) - -(defun lsp--make-symbol-highlight-callback (buf) - "Create a callback to process the reply of a -'textDocument/documentHightlight' message for the buffer BUF. -A reference is highlighted only if it is visible in a window." - (cl-check-type buf buffer) - (lambda (highlights) - (with-current-buffer buf - (lsp--remove-cur-overlays) - (when (and highlights (/= (length highlights) 0)) - (let* ((windows-on-buffer (get-buffer-window-list nil nil 'visible)) - (overlays (lsp--workspace-highlight-overlays lsp--cur-workspace)) - (buf-overlays (gethash (current-buffer) overlays)) - wins-visible-pos) - (save-restriction - (widen) - ;; Save visible portions of the buffer - (dolist (win windows-on-buffer) - (let* ((win-start (window-start win)) - (win-end (window-end win))) - (push (cons (1- (line-number-at-pos win-start)) - (1+ (line-number-at-pos win-end))) - wins-visible-pos))) - (seq-doseq (highlight highlights) - (let* ((range (gethash "range" highlight nil)) - (kind (gethash "kind" highlight 1)) - (start (gethash "start" range)) - (end (gethash "end" range)) - overlay) - (dolist (win wins-visible-pos) - (let* ((start-window (car win)) - (end-window (cdr win))) - ;; Make the overlay only if the reference is visible - (when (and (> (1+ (gethash "line" start)) start-window) - (< (1+ (gethash "line" end)) end-window)) - (setq overlay (make-overlay (lsp--position-to-point start) - (lsp--position-to-point end))) - (overlay-put overlay 'face - (cdr (assq kind lsp--highlight-kind-face))) - (push overlay buf-overlays) - (puthash (current-buffer) buf-overlays overlays)))))))))))) - -(defconst lsp--symbol-kind - '((1 . "File") - (2 . "Module") - (3 . "Namespace") - (4 . "Package") - (5 . "Class") - (6 . "Method") - (7 . "Property") - (8 . "Field") - (9 . "Constructor"), - (10 . "Enum") - (11 . "Interface") - (12 . "Function") - (13 . "Variable") - (14 . "Constant") - (15 . "String") - (16 . "Number") - (17 . "Boolean") - (18 . "Array") - (19 . "Object") - (20 . "Key") - (21 . "Null") - (22 . "Enum Member") - (23 . "Struct") - (24 . "Event") - (25 . "Operator") - (26 . "Type Parameter"))) - -(defun lsp--symbol-information-to-xref (symbol) - "Return a `xref-item' from SYMBOL information." - (let* ((location (gethash "location" symbol)) - (uri (gethash "uri" location)) - (range (gethash "range" location)) - (start (gethash "start" range))) - (xref-make (format "[%s] %s" - (alist-get (gethash "kind" symbol) lsp--symbol-kind) - (gethash "name" symbol)) - (xref-make-file-location (lsp--uri-to-path uri) - (1+ (gethash "line" start)) - (gethash "character" start))))) - -(defun lsp-format-region (s e) - (let ((edits (lsp--send-request (lsp--make-request - "textDocument/rangeFormatting" - (lsp--make-document-range-formatting-params s e))))) - (lsp--apply-text-edits edits))) - -(defun lsp--location-to-td-position (location) - "Convert LOCATION to a TextDocumentPositionParams object." - `(:textDocument (:uri ,(gethash "uri" location)) - :position ,(gethash "start" (gethash "range" location)))) - -(defun lsp--symbol-info-to-identifier (symbol) - (let ((td-params (lsp--location-to-td-position (gethash "location" symbol)))) - (propertize (gethash "name" symbol) - 'ref-params (lsp--make-reference-params td-params) - 'def-params td-params))) - -(defun lsp--get-document-symbols () - (lsp--cur-workspace-check) - (lsp--send-request (lsp--make-request - "textDocument/documentSymbol" - `(:textDocument ,(lsp--text-document-identifier))))) - -(defun lsp--xref-backend () 'xref-lsp) - -(cl-defmethod xref-backend-identifier-at-point ((_backend (eql xref-lsp))) - (propertize (symbol-name (symbol-at-point)) - 'def-params (lsp--text-document-position-params) - 'ref-params (lsp--make-reference-params))) - -(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql xref-lsp))) - (let ((json-false :json-false) - (symbols (lsp--get-document-symbols))) - (seq-map #'lsp--symbol-info-to-identifier symbols))) - -;; (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql xref-lsp))) -;; nil) - -(cl-defmethod xref-backend-definitions ((_backend (eql xref-lsp)) identifier) - (let* ((maybeparams (get-text-property 0 'def-params identifier)) - ;; In some modes (such as haskell-mode), xref-find-definitions gets - ;; called directly without applying the properties expected here. So we - ;; must test if the properties are present, and if not use the current - ;; point location. - (params (if (null maybeparams) - (lsp--text-document-position-params) - maybeparams)) - (defs (lsp--send-request (lsp--make-request - "textDocument/definition" - params)))) - (lsp--locations-to-xref-items (if (listp defs) defs (list defs))))) - -(cl-defmethod xref-backend-references ((_backend (eql xref-lsp)) identifier) - (let* ((properties (text-properties-at 0 identifier)) - (params (plist-get properties 'ref-params)) - (refs (lsp--send-request (lsp--make-request - "textDocument/references" - (or params (lsp--make-reference-params)))))) - (lsp--locations-to-xref-items refs))) - -(cl-defmethod xref-backend-apropos ((_backend (eql xref-lsp)) pattern) - (let ((symbols (lsp--send-request (lsp--make-request - "workspace/symbol" - `(:query ,pattern))))) - (seq-map #'lsp--symbol-information-to-xref symbols))) - -(defun lsp--make-document-rename-params (newname) - "Make DocumentRangeFormattingParams for selected region. -interface RenameParams { - textDocument: TextDocumentIdentifier; - position: Position; - newName: string; -}" - `(:position ,(lsp--cur-position) - :textDocument ,(lsp--text-document-identifier) - :newName ,newname)) - -(defun lsp-rename (newname) - "Rename the symbol (and all references to it) under point to NEWNAME." - (interactive (list (read-string "Rename to: " (thing-at-point 'symbol)))) - (lsp--cur-workspace-check) - (unless (lsp--capability "renameProvider") - (signal 'lsp-capability-not-supported (list "renameProvider"))) - (let ((edits (lsp--send-request (lsp--make-request - "textDocument/rename" - (lsp--make-document-rename-params newname))))) - (when edits - (lsp--apply-workspace-edit edits)))) - -(defun lsp-find-custom (method &optional extra) - "Send request named METHOD and get cross references of the symbol under point. -EXTRA is a plist of extra parameters." - (let ((loc (lsp--send-request - (lsp--make-request method - (append (lsp--text-document-position-params) extra))))) - (if loc - (xref--show-xrefs - (lsp--locations-to-xref-items (if (listp loc) loc (list loc))) nil) - (message "Not found for: %s" (thing-at-point 'symbol t))))) - -(defun lsp-goto-implementation () - "Resolve, and go to the implementation(s) of the symbol under point." - (interactive) - (lsp--cur-workspace-check) - (unless (lsp--capability "implementationProvider") - (signal 'lsp-capability-not-supported (list "implementationProvider"))) - (lsp-find-custom "textDocument/implementation")) - -(defun lsp-goto-type-definition () - "Resolve, and go to the type definition(s) of the symbol under point." - (interactive) - (lsp--cur-workspace-check) - (unless (lsp--capability "typeDefinitionProvider") - (signal 'lsp-capability-not-supported (list "typeDefinitionProvider"))) - (lsp-find-custom "textDocument/typeDefinition")) - -(define-inline lsp--execute-command (command) - "Given a COMMAND returned from the server, create and send a -'workspace/executeCommand' message." - (inline-letevals (command) - (inline-quote - (progn - (cl-check-type ,command (satisfies lsp--command-p)) - (lsp--send-execute-command - (gethash "command" ,command) - (gethash "arguments" ,command nil)))))) - -(defun lsp--send-execute-command (command &optional args) - "Create and send a 'workspace/executeCommand' message having -command COMMAND and optionsl ARGS" - (lsp--cur-workspace-check) - (unless (or (lsp--capability "executeCommandProvider") - (lsp--registered-capability "workspace/executeCommand")) - (signal 'lsp-capability-not-supported (list "executeCommandProvider"))) - (lsp--send-request - (lsp--make-request - "workspace/executeCommand" - (lsp--make-execute-command-params command args)))) - -(defun lsp--make-execute-command-params (cmd &optional args) - (if args - (list :command cmd :arguments args) - (list :command cmd))) - -(defalias 'lsp-point-to-position #'lsp--point-to-position) -(defalias 'lsp-get-start-position #'lsp--get-start-position) -(defalias 'lsp-get-end-position #'lsp--get-end-position) -(defalias 'lsp-text-document-identifier #'lsp--text-document-identifier) -(defalias 'lsp-send-execute-command #'lsp--send-execute-command) -(defalias 'lsp-on-open #'lsp--text-document-did-open) -(defalias 'lsp-on-save #'lsp--text-document-did-save) -;; (defalias 'lsp-on-change #'lsp--text-document-did-change) -(defalias 'lsp-completion-at-point #'lsp--get-completions) - -(defun lsp--unset-variables () - (when lsp-enable-eldoc - (setq-local eldoc-documentation-function 'ignore)) - (when lsp-enable-xref - (setq-local xref-backend-functions nil)) - (when lsp-enable-completion-at-point - (remove-hook 'completion-at-point-functions #'lsp-completion-at-point t)) - (remove-hook 'after-change-functions #'lsp-on-change t) - (remove-hook 'after-revert-hook #'lsp-on-revert t) - (remove-hook 'before-change-functions #'lsp-before-change t)) - -(defun lsp--set-configuration (settings) - "Set the configuration for the lsp server." - (lsp--send-notification (lsp--make-notification - "workspace/didChangeConfiguration" - `(:settings , settings)))) - -(defun lsp-workspace-register-watch (to-watch &optional workspace) - "Monitor for file change and trigger workspace/didChangeConfiguration. - -TO-WATCH is a list of the directories and regexp in the following format: -'((root-dir1 (glob-pattern1 glob-pattern2)) - (root-dir2 (glob-pattern3 glob-pattern4))) - -If WORKSPACE is not specified the `lsp--cur-workspace' will be used." - (setq workspace (or workspace lsp--cur-workspace)) - (let ((watches (lsp--workspace-watches workspace))) - (cl-loop for (dir glob-patterns) in to-watch do - (lsp-create-watch - dir - (mapcar 'eshell-glob-regexp glob-patterns) - (lambda (event) - (let ((lsp--cur-workspace workspace)) - (lsp-send-notification - (lsp-make-notification - "workspace/didChangeWatchedFiles" - (list :changes - (list - :type (alist-get (cadr event) lsp--file-change-type) - :uri (lsp--path-to-uri (caddr event)))))))) - watches)))) - -(declare-function lsp-mode "lsp-mode" (&optional arg)) - -(provide 'lsp-methods) -;;; lsp-methods.el ends here |