Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 67 additions & 34 deletions lsp-python-ms.el
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,23 @@
;; from https://vxlabs.com/2018/11/19/configuring-emacs-lsp-mode-and-microsofts-visual-studio-code-python-language-server/

;;; Code:
(require 'cl)

(defvar lsp-python-ms-dir nil
"Path to langeuage server directory containing
Microsoft.Python.LanguageServer.dll")
"Path to langeuage server directory containing Microsoft.Python.LanguageServer.dll")

(defvar lsp-python-ms-dotnet nil
"Path to dotnet executable.")

(defvar lsp-python-ms-executable nil
"Path to Microsoft.Python.LanguageServer.exe")

;; it's crucial that we send the correct Python version to MS PYLS,
;; else it returns no docs in many cases furthermore, we send the
;; current Python's (can be virtualenv) sys.path as searchPaths

(defun lsp-python-ms--get-python-ver-and-syspath (workspace-root)
"return list with pyver-string and json-encoded list of python
"Return list with pyver-string and json-encoded list of python
search paths."
(let ((python (executable-find python-shell-interpreter))
(init "from __future__ import print_function; import sys; import json;")
Expand All @@ -55,50 +58,80 @@ search paths."
;; I based most of this on the vs.code implementation:
;; https://github.com/Microsoft/vscode-python/blob/master/src/client/activation/languageServer/languageServer.ts#L219
;; (it still took quite a while to get right, but here we are!)
(defun lsp-python-ms--extra-init-params (workspace)
(destructuring-bind (pyver pysyspath)
(lsp-python-ms--get-python-ver-and-syspath (lsp--workspace-root workspace))
`(:interpreter
(:properties (
:InterpreterPath ,(executable-find python-shell-interpreter)
;; this database dir will be created if required
:DatabasePath ,(expand-file-name (concat lsp-python-ms-dir "db/"))
:Version ,pyver))
;; preferredFormat "markdown" or "plaintext"
;; experiment to find what works best -- over here mostly plaintext
:displayOptions (
:preferredFormat "plaintext"
:trimDocumentationLines :json-false
:maxDocumentationLineLength 0
:trimDocumentationText :json-false
:maxDocumentationTextLength 0)
:searchPaths ,(json-read-from-string pysyspath))))
(defun lsp-python-ms--extra-init-params (&optional workspace)
(let ((workspace-root (if workspace (lsp--workspace-root workspace) (pwd))))
(destructuring-bind (pyver pysyspath)
(lsp-python-ms--get-python-ver-and-syspath workspace-root)
`(:interpreter
(:properties (:InterpreterPath ,(executable-find python-shell-interpreter)
;; this database dir will be created if required
:DatabasePath ,(expand-file-name (concat lsp-python-ms-dir "db/"))
:Version ,pyver))
;; preferredFormat "markdown" or "plaintext"
;; experiment to find what works best -- over here mostly plaintext
:displayOptions (:preferredFormat "plaintext"
:trimDocumentationLines :json-false
:maxDocumentationLineLength 0
:trimDocumentationText :json-false
:maxDocumentationTextLength 0)
:searchPaths ,(json-read-from-string pysyspath)))))

(defun lsp-python-ms--client-initialized (client)
"Callback for client initialized."
(lsp-client-on-notification client "python/languageServerStarted" 'lsp-python-ms--language-server-started))

(defun lsp-python-ms--language-server-started (workspace params)
"Callback for server started initialized."
(message "[MS Python language server started]"))

(defun lsp-python-ms--workspace-root ()
"Get the root, or just return `default-directory'."
(let ((proj (projectile-project-root)))
(let ((proj (if (fboundp 'projectile-project-root) (projectile-project-root) nil)))
(if proj proj default-directory)))

(defun lsp-python-ms--find-dotnet ()
"Get the path to dotnet, or return `lsp-python-ms-dotnet'."
(let ((dotnet (executable-find "dotnet")))
(let ((dotnet (if (eq system-type 'windows-nt) "dotnet" (executable-find "dotnet"))))
(if dotnet dotnet lsp-python-ms-dotnet)))

(defun lsp-python-ms--filter-nbsp (str)
"Filter nbsp entities from STR."
(replace-regexp-in-string " " " " str))
(let ((rx " "))
(when (eq system-type 'windows-nt)
(setq rx (concat rx "\\|\r")))
(replace-regexp-in-string rx " " str)))

(setq lsp-render-markdown-markup-content #'lsp-python-ms--filter-nbsp)
(advice-add 'lsp-ui-doc--extract
:filter-return #'lsp-python-ms--filter-nbsp)

(lsp-define-stdio-client
lsp-python "python"
#'lsp-python-ms--workspace-root
`(,(lsp-python-ms--find-dotnet) ,(concat lsp-python-ms-dir "Microsoft.Python.LanguageServer.dll"))
:extra-init-params #'lsp-python-ms--extra-init-params)

(advice-add 'lsp-ui-doc--extract :filter-return #'lsp-python-ms--filter-nbsp)

(defun lsp-python-ms--command-string ()
"Return the command that starts the server."
(if lsp-python-ms-executable
lsp-python-ms-executable
(list (lsp-python-ms--find-dotnet)
(concat lsp-python-ms-dir "Microsoft.Python.LanguageServer.dll"))))

;;; Old lsp-mode
(unless (fboundp 'lsp-register-client)
(lsp-define-stdio-client
lsp-python "python"
#'lsp-python-ms--workspace-root
nil
:command-fn 'lsp-python-ms--command-string
:extra-init-params #'lsp-python-ms--extra-init-params
:initialize #'lsp-python-ms--client-initialized))

;;; New lsp-mode
(when (fboundp 'lsp-register-client)
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection 'lsp-python-ms--command-string)
:major-modes '(python-mode)
:server-id 'mspyls
:initialization-options 'lsp-python-ms--extra-init-params
:notification-handlers (lsp-ht ("python/languageServerStarted" 'lsp-python-ms--language-server-started))
)))

(provide 'lsp-python-ms)

;;; lsp-python.el ends here
;;; lsp-python-ms.el ends here