about summary refs log tree commit diff
path: root/configs/shared/emacs/.emacs.d/elpa/lsp-mode-20180911.1829/lsp-mode.el
blob: 2a7e4b25b5761a40a273f57e5e311d0373356a55 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
;;; 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