;;; lsp-mode.el --- Minor mode for interacting with Language Servers -*- lexical-binding: t -*- ;; Copyright (C) 2016 Vibhav Pant ;; 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 . ;; Author: Vibhav Pant ;; URL: https://github.com/emacs-lsp/lsp-mode ;; Package-Requires: ((emacs "25.1") (flycheck "30")) ;; Version: 3.1 ;;; Commentary: ;;; Code: (require 'lsp-methods) (require 'lsp-receive) (require 'lsp-send) (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") (defun lsp--make-stdio-connection (name command command-fn) (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)))) (make-process :name name :connection-type 'pipe :coding 'no-conversion :command final-command :filter filter :sentinel sentinel :stderr (generate-new-buffer-name (concat "*" name " stderr*")))))) (defun lsp--make-tcp-connection (name command command-fn host port) (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 :coding 'no-conversion :command final-command :sentinel sentinel :stderr (generate-new-buffer-name (concat "*" name " stderr*"))) tcp-proc (open-network-stream (concat name " TCP connection") nil host port :type 'plain)) (set-process-filter tcp-proc filter) (cons proc tcp-proc)))) (defun lsp--verify-regexp-list (l) (cl-assert (cl-typep l 'list) nil "lsp-define-client: :ignore-regexps is not a list") (dolist (e l l) (cl-assert (cl-typep e 'string) nil (format "lsp-define-client: :ignore-regexps element %s is not a string" e)))) (defmacro lsp-define-stdio-client (name language-id get-root command &rest args) "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: `:ignore-regexps' is a list of regexps which when matched will be ignored by the output parser. `: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. `:initialize' is a function called when the client is intiailized. It takes a single argument, the newly created client. " (let ((enable (intern (format "%s-enable" name)))) `(defun ,enable () ,(plist-get args :docstring) (interactive) (let ((client (make-lsp--client :language-id ,(lsp--assert-type language-id #'stringp) :send-sync #'lsp--stdio-send-sync :send-async #'lsp--stdio-send-async :new-connection (lsp--make-stdio-connection ,(symbol-name name) ,command ,(plist-get args :command-fn)) :get-root ,get-root :ignore-regexps ,(plist-get args :ignore-regexps)))) (unless lsp-mode ,(when (plist-get args :initialize) `(funcall ,(plist-get args :initialize) client)) (let ((root (funcall (lsp--client-get-root client)))) (if (lsp--should-start-p root) (progn (lsp-mode 1) (lsp--start client)) (message "Not initializing project %s" root)))))))) (defmacro lsp-define-tcp-client (name language-id get-root command host port &rest args) "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 which when matched will be ignored by the output parser. `: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. `:initialize' is a function called when the client is intiailized. It takes a single argument, the newly created client." (let ((enable (intern (format "%s-enable" name)))) `(defun ,enable () ,(plist-get args :docstring) (interactive) (let ((client (make-lsp--client :language-id ,(lsp--assert-type language-id #'stringp) :send-sync #'lsp--stdio-send-sync :send-async #'lsp--stdio-send-async :new-connection (lsp--make-tcp-connection ,(symbol-name name) ,command ,(plist-get args :command-fn) ,host ,port) :get-root ,get-root :ignore-regexps ,(plist-get args :ignore-regexps)))) (unless lsp-mode ,(when (plist-get args :initialize) `(funcall ,(plist-get args :initialize) client)) (let ((root (funcall (lsp--client-get-root client)))) (if (lsp--should-start-p root) (progn (lsp-mode 1) (lsp--start client)) (message "Not initializing project %s" root)))))))) ;;;###autoload (define-minor-mode lsp-mode "" nil nil nil :lighter " LSP" :group 'lsp-mode) (defconst lsp--sync-type `((0 . "None") (1 . "Full Document") (2 . "Incremental Changes"))) (defconst lsp--capabilities `(("textDocumentSync" . ("Document sync method" . ((1 . "None") (2 . "Send full contents") (3 . "Send incremental changes.")))) ("hoverProvider" . ("The server provides hover support" . boolean)) ("completionProvider" . ("The server provides completion support" . boolean)) ("definitionProvider" . ("The server provides goto definition 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)) (("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