about summary refs log blame commit diff
path: root/configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180827.344/lsp-mode.el
blob: 2a7e4b25b5761a40a273f57e5e311d0373356a55 (plain) (tree)
















































































































































































































































































































































































































                                                                                                   
;;; lsp-mode.el --- Minor mode for interacting with Language Servers -*- lexical-binding: t -*-

;; Copyright (C) 2016-2018  Vibhav Pant <vibhavp@gmail.com>

;; 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/>.

;; Author: Vibhav Pant <vibhavp@gmail.com>
;; URL: https://github.com/emacs-lsp/lsp-mode
;; Package-Requires: ((emacs "25.1"))
;; Version: 4.2

;;; Commentary:

;;; Code:

(require 'lsp-methods)
(require 'lsp-io)
(require 'cl-lib)
(require 'network-stream)

(defvar lsp-version-support "3.0"
  "This is the version of the Language Server Protocol currently supported by ‘lsp-mode’.")

;;;###autoload
(define-minor-mode lsp-mode ""
  nil nil nil
  :lighter (:eval (lsp-mode-line))
  :group 'lsp-mode)

(defun lsp--make-stdio-connection (name command command-fn stderr)
  (lambda (filter sentinel)
    (let* ((command (if command-fn (funcall command-fn) command))
           (final-command (if (consp command) command (list command))))
      (unless (executable-find (nth 0 final-command))
        (error (format "Couldn't find executable %s" (nth 0 final-command))))
      (let ((proc (make-process
                    :name name
                    :connection-type 'pipe
                    :coding 'no-conversion
                    :command final-command
                    :filter filter
                    :sentinel sentinel
                    :stderr stderr
                    :noquery t)))
        ;; TODO: This is redundant with :noquery above, but due to a
        ;; bug pre-Emacs 26 it is still needed
        ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=30031
        (set-process-query-on-exit-flag (get-buffer-process (get-buffer stderr)) nil)
        proc))))

(defun lsp--make-tcp-connection (name command command-fn host port stderr)
  (lambda (filter sentinel)
    (let* ((command (if command-fn (funcall command-fn) command))
            (final-command (if (consp command) command (list command)))
            proc tcp-proc)
      (unless (executable-find (nth 0 final-command))
        (error (format "Couldn't find executable %s" (nth 0 final-command))))
      (setq proc (make-process
                   :name name
                   :connection-type 'pipe
                   :coding 'no-conversion
                   :command final-command
                   :sentinel sentinel
                   :stderr stderr
                   :noquery t)
        tcp-proc (open-network-stream (concat name " TCP connection")
                   nil host port
                   :type 'plain))
      ;; TODO: Same :noquery issue (see above)
      (set-process-query-on-exit-flag (get-buffer-process (get-buffer stderr)) nil)
      (set-process-query-on-exit-flag tcp-proc nil)
      (set-process-filter tcp-proc filter)
      (cons proc tcp-proc))))

(cl-defmacro lsp-define-whitelist-add (name get-root
                                               &key docstring)
  "Define a function to add the project root for the current buffer to the whitleist.
NAME is the base name for the command.
GET-ROOT is the language-specific function to determine the project root for the current buffer."
  (let ((whitelist-add      (intern (format "%s-whitelist-add" name)))
        (enable-interactive (intern (format "%s-enable" name))))
    `(defun ,whitelist-add ()
       ,docstring
       (interactive)
       (let ((root (funcall ,get-root)))
         (customize-save-variable 'lsp-project-whitelist
           (add-to-list 'lsp-project-whitelist (lsp--as-regex root)))
         (,enable-interactive)))))

(cl-defmacro lsp-define-whitelist-remove (name get-root
                                            &key docstring)
  "Define a function to remove the project root for the current buffer from the whitleist.
NAME is the base name for the command.
GET-ROOT is the language-specific function to determine the project root for the current buffer."
  (let ((whitelist-remove (intern (format "%s-whitelist-remove" name))))
    `(defun ,whitelist-remove ()
       ,docstring
       (interactive)
       (let ((root (funcall ,get-root)))
         (customize-save-variable 'lsp-project-whitelist
           (remove (lsp--as-regex root) lsp-project-whitelist))))))

(defun lsp--as-regex (root)
  "Convert the directory path in ROOT to an equivalent regex."
  (concat "^" (regexp-quote root) "$"))

(cl-defmacro lsp-define-stdio-client (name language-id get-root command
                                       &key docstring
                                       language-id-fn
                                       command-fn
                                       ignore-regexps
                                       ignore-messages
                                       extra-init-params
                                       initialize
                                       prefix-function)
  "Define a LSP client using stdio.
NAME is the symbol to use for the name of the client.
LANGUAGE-ID is the language id to be used when communication with
the Language Server.  COMMAND is the command to run.

Optional arguments:
`:docstring' is an optional docstring used for the entrypoint function created by
`lsp-define-stdio-client'.

`:ignore-regexps' is a list of regexps.  When a data packet from the LSP server
 matches any of these regexps, it will be ignored.  This is intended for dealing
 with LSP servers that output non-protocol data.

`:ignore-messages' is a list of regexps.  When a message from the LSP 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 `logMessage's.

`:command-fn' is a function that returns the command string/list to be used to
 launch the language server. If non-nil, COMMAND is ignored.

`:language-id-fn' is a function that returns the language-id string to be used
 while opening a new file. If non-nil, LANGUAGE-ID is ignored.

`:extra-init-params' is a plist that specifies any (optional)
 initializeOptions parameters required by the LSP server. A function taking
 a single argument (LSP workspace) and returning a plist is also accepted.

`:initialize' is a function called when the client is initialized. It takes a
 single argument, the newly created client.

`:prefix-function' is a function 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."
  (cl-check-type name symbol)
  (let ((enable-name (intern (format "%s-enable" name))))
    `(progn
       (lsp-define-whitelist-add ,name ,get-root)
       (lsp-define-whitelist-remove ,name ,get-root)
       (defun ,enable-name ()
         ,docstring
         (interactive)
         (lsp--enable-stdio-client ',name
           :language-id ,language-id
           :language-id-fn ,language-id-fn
           :root-directory-fn ,get-root
           :command ,command
           :command-fn ,command-fn
           :ignore-regexps ,ignore-regexps
           :ignore-messages ,ignore-messages
           :extra-init-params ,extra-init-params
           :initialize-fn ,initialize
           :enable-function (function ,enable-name)
           :prefix-function ,prefix-function)))))

(cl-defun lsp--enable-stdio-client (name &key language-id language-id-fn
                                         root-directory-fn command command-fn
                                         ignore-regexps ignore-messages
                                         extra-init-params initialize-fn
                                         enable-function
                                         prefix-function)
  (cl-check-type name symbol)
  (cl-check-type language-id (or null string))
  (cl-check-type language-id-fn (or null function))
  (cl-check-type root-directory-fn (or null function))
  (cl-check-type command list)
  (cl-check-type command-fn (or null function))
  (cl-check-type ignore-regexps list)
  (cl-check-type ignore-messages list)
  (cl-check-type extra-init-params (or list function))
  (cl-check-type initialize-fn (or null function))
  ;; (cl-check-type enable-function function)
  (cl-check-type prefix-function (or null function))
  (when (and (not lsp-mode) (buffer-file-name))
    (let* ((stderr (generate-new-buffer-name
                    (concat "*" (symbol-name name) " stderr*")))
           (client (make-lsp--client
                    :language-id (or language-id-fn (lambda (_) language-id))
                    :new-connection (lsp--make-stdio-connection
                                     (symbol-name name)
                                     command
                                     command-fn
                                     stderr)
                    :stderr stderr
                    :get-root root-directory-fn
                    :ignore-regexps ignore-regexps
                    :ignore-messages ignore-messages
                    :enable-function enable-function
                    :prefix-function prefix-function)))
      (when initialize-fn
        (funcall initialize-fn client))
      (let ((root (funcall (lsp--client-get-root client))))
        (if (lsp--should-start-p root)
            (lsp--start client extra-init-params)
          (message "Not initializing project %s" root))))))

(cl-defmacro lsp-define-tcp-client (name language-id get-root command host port
                                     &key docstring
                                     language-id-fn
                                     command-fn
                                     ignore-regexps
                                     ignore-messages
                                     extra-init-params
                                     initialize
                                     prefix-function)
  "Define a LSP client using TCP.
NAME is the symbol to use for the name of the client.
LANGUAGE-ID is the language id to be used when communication with
the Language Server.  COMMAND is the command to run.  HOST is the
host address.  PORT is the port number.

Optional arguments:
`:ignore-regexps' is a list of regexps.  When a data packet from the LSP server
 matches any of these regexps, it will be ignored.  This is intended for dealing
 with LSP servers that output non-protocol data.

`:ignore-messages' is a list of regexps.  When a message from the LSP 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 `logMessage's.

`:command-fn' is a function that returns the command string/list to be used to
 launch the language server. If non-nil, COMMAND is ignored.

`:language-id-fn' is a function that returns the language-id string to be used
 while opening a new file. If non-nil, LANGUAGE-ID is ignored.

`:extra-init-params' is a plist that specifies any (optional)
 initializeOptions parameters required by the LSP server. A function taking
 a single argument (LSP workspace) and returning a plist is also accepted.

`:initialize' is a function called when the client is initialized. It takes a
  single argument, the newly created client.

`:prefix-function' is a function 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."
  (cl-check-type name symbol)
  (let ((enable-name (intern (format "%s-enable" name))))
    `(progn
       (lsp-define-whitelist-add ,name ,get-root)
       (lsp-define-whitelist-remove ,name ,get-root)
       (defun ,enable-name ()
         ,docstring
         (interactive)
         (lsp--enable-tcp-client ',name
           :language-id ,language-id
           :language-id-fn ,language-id-fn
           :root-directory-fn ,get-root
           :command ,command
           :command-fn ,command-fn
           :host ,host
           :port ,port
           :ignore-regexps ,ignore-regexps
           :ignore-messages ,ignore-messages
           :extra-init-params ,extra-init-params
           :initialize-fn ,initialize
           :enable-function (function ,enable-name)
           :prefix-function ,prefix-function)))))

(cl-defun lsp--enable-tcp-client (name &key language-id language-id-fn
                                       root-directory-fn command command-fn
                                       host port
                                       ignore-regexps ignore-messages
                                       extra-init-params initialize-fn
                                       enable-function
                                       prefix-function)
  (cl-check-type name symbol)
  (cl-check-type language-id (or null string))
  (cl-check-type language-id-fn (or null function))
  (cl-check-type root-directory-fn (or null function))
  (cl-check-type command list)
  (cl-check-type command-fn (or null function))
  (cl-check-type host string)
  (cl-check-type port (integer 1 #xFFFF))
  (cl-check-type ignore-regexps list)
  (cl-check-type ignore-messages list)
  (cl-check-type extra-init-params (or list function))
  (cl-check-type initialize-fn (or null function))
  (cl-check-type prefix-function (or null function))
  (when (and (not lsp-mode) (buffer-file-name))
    (let* ((stderr (generate-new-buffer-name
                    (concat "*" (symbol-name name) " stderr*")))
           (client (make-lsp--client
                    :language-id (or language-id-fn (lambda (_) language-id))
                    :new-connection (lsp--make-tcp-connection
                                     (symbol-name name)
                                     command
                                     command-fn
                                     host port
                                     stderr)
                    :stderr stderr
                    :get-root root-directory-fn
                    :ignore-regexps ignore-regexps
                    :ignore-messages ignore-messages
                    :enable-function enable-function
                    :prefix-function prefix-function)))
      (when initialize-fn
        (funcall initialize-fn client))
      (let ((root (funcall (lsp--client-get-root client))))
        (if (lsp--should-start-p root)
            (lsp--start client extra-init-params)
          (message "Not initializing project %s" root))))))

(defvar-local lsp-status nil
  "The current status of the LSP server.")

(defun lsp-workspace-status (status-string &optional workspace)
  "Set current workspace status to STATUS-STRING.
If WORKSPACE is not specified defaults to lsp--cur-workspace."
  (setf (lsp--workspace-status (or workspace lsp--cur-workspace)) status-string))

(defun lsp-mode-line ()
  "Construct the mode line text."
  (concat " LSP" lsp-status (lsp--workspace-status lsp--cur-workspace)))

(defconst lsp--sync-type
  `((0 . "None")
    (1 . "Full Document")
    (2 . "Incremental Changes")))

(defconst lsp--capabilities
  `(("textDocumentSync" . ("Document sync method" .
                           ((0 . "None")
                            (1 . "Send full contents")
                            (2 . "Send incremental changes."))))
    ("hoverProvider" . ("The server provides hover support" . boolean))
    ("completionProvider" . ("The server provides completion support" . boolean))
    ("signatureHelpProvider" . ("The server provides signature help support" . boolean))
    ("definitionProvider" . ("The server provides goto definition support" . boolean))
    ("typeDefinitionProvider" . ("The server provides goto type definition support" . boolean))
    ("implementationProvider" . ("The server provides goto implementation support" . boolean))
    ("referencesProvider" . ("The server provides references support" . boolean))
    (("documentHighlightProvider" . ("The server provides document highlight support." . boolean)))
    ("documentSymbolProvider" . ("The server provides file symbol support" . boolean))
    ("workspaceSymbolProvider" . ("The server provides project symbol support" . boolean))
    ("codeActionProvider" . ("The server provides code actions" . boolean))
    ("codeLensProvider" . ("The server provides code lens" . boolean))
    ("documentFormattingProvider" . ("The server provides file formatting" . boolean))
    ("documentOnTypeFormattingProvider" . ("The server provides on-type formatting" . boolean))
    ("documentLinkProvider" . ("The server provides document link support" . boolean))
    ("executeCommandProvider" . ("The server provides command execution support" . boolean))
    (("documentRangeFormattingProvider" . ("The server provides region formatting" . boolean)))
    (("renameProvider" . ("The server provides rename support" . boolean)))))

(defun lsp--cap-str (cap)
  (let* ((elem (assoc cap lsp--capabilities))
         (desc (cadr elem))
         (type (cddr elem))
         (value (gethash cap (lsp--server-capabilities))))
    (when (and elem desc type value)
      (concat desc (cond
                    ((listp type) (concat ": " (cdr (assoc value type))))) "\n"))))

(defun lsp-capabilities ()
  "View all capabilities for the language server associated with this buffer."
  (interactive)
  (unless lsp--cur-workspace
    (user-error "No language server is associated with this buffer"))
  (let ((str (mapconcat #'lsp--cap-str (reverse (hash-table-keys
                                                 (lsp--server-capabilities))) ""))
        (buffer-name (generate-new-buffer-name "lsp-capabilities"))
        )
    (get-buffer-create buffer-name)
    (with-current-buffer buffer-name
      (view-mode -1)
      (erase-buffer)
      (insert str)
      (view-mode 1))
    (switch-to-buffer buffer-name)))

(provide 'lsp-mode)
;;; lsp-mode.el ends here