Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.

Setup MS PYLS automatically. #37

Merged
merged 3 commits into from
Jun 30, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 16 additions & 45 deletions README.org
Original file line number Diff line number Diff line change
@@ -1,50 +1,21 @@
lsp-mode client leveraging microsoft's [[https://github.com/Microsoft/python-language-server][python-language-server]]
=lsp-mode= client leveraging microsoft's [[https://github.com/Microsoft/python-language-server][python-language-server]]

* Installation

1. Install [[https://www.microsoft.com/net/download][dotnet-sdk]]
2. Clone and install [[https://github.com/Microsoft/python-language-server][python-language-server]]:
#+BEGIN_SRC bash
git clone https://github.com/Microsoft/python-language-server.git
cd python-language-server/src/LanguageServer/Impl
dotnet build -c Release
#+END_SRC

If you choose, compile the language server to a single executable
with:
#+BEGIN_SRC bash
dotnet publish -c Release -r osx-x64 # mac
#+END_SRC

change the value of the ~-r~ flag depending on your architecture and
operating system. See Microsoft's [[https://docs.microsoft.com/en-us/dotnet/core/rid-catalog][Runtime ID Catalog]] for the right
value for your system.

Then, link the executable to somewhere on your path, e.g.
#+BEGIN_SRC bash
ln -sf $(git rev-parse --show-toplevel)/output/bin/Release/osx-x64/publish/Microsoft.Python.LanguageServer ~/.local/bin/
#+END_SRC
note that, on some systems (for example, Fedora), the executable comes out as
~Microsoft.Python.LanguageServer.LanguageServer~.

3. Include ~lsp-python-ms~ in your config in your preferred manner. A
minimal ~use-package~ initialization might be:

#+BEGIN_SRC elisp
(use-package lsp-python-ms
:ensure nil
:hook (python-mode . lsp)
:config

;; for dev build of language server
(setq lsp-python-ms-dir
(expand-file-name "~/python-language-server/output/bin/Release/"))
;; for executable of language server, if it's not symlinked on your PATH
(setq lsp-python-ms-executable
"~/python-language-server/output/bin/Release/osx-x64/publish/Microsoft.Python.LanguageServer"))
#+END_SRC

For developement, you might find it useful to run =cask install=.
Include ~lsp-python-ms~ in the configuration file:
#+BEGIN_SRC emacs-lisp
(require lsp-python-ms)
(add-hook 'python-mode #'lsp) ; or lsp-deferred
#+END_SRC

A minimal ~use-package~ initialization might be:
#+BEGIN_SRC elisp
(use-package lsp-python-ms
:ensure t
:demand
:hook (python-mode . lsp)) ; or lsp-deferred
#+END_SRC

For development, you might find it useful to run =cask install=.
* Credit

All credit to [[https://cpbotha.net][cpbotha]] on [[https://vxlabs.com/2018/11/19/configuring-emacs-lsp-mode-and-microsofts-visual-studio-code-python-language-server/][vxlabs]]! This just tidies and packages his
Expand Down
125 changes: 88 additions & 37 deletions lsp-python-ms.el
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

;; Author: Charl Botha
;; Maintainer: Andrew Christianson
;; Version: 0.1.0
;; Version: 0.2.0
;; Package-Requires: ((cl-lib "0.6.1") (lsp-mode "6.0") (python "0.26.1") (json "1.4") (emacs "24.4"))
;; Homepage: https://github.com/andrew-christianson/lsp-python-ms
;; Keywords: languages tools
Expand Down Expand Up @@ -39,7 +39,7 @@
;; forward declare variable
(defvar lsp-render-markdown-markup-content)

(defvar lsp-python-ms-dir nil
(defvar lsp-python-ms-dir (expand-file-name "mspyls/" user-emacs-directory)
"Path to language server directory.

This is the directory containing Microsoft.Python.LanguageServer.dll.")
Expand All @@ -53,20 +53,6 @@ This is the directory containing Microsoft.Python.LanguageServer.dll.")
;; If this is nil, the language server will write cache files in a directory
;; sibling to the root of every project you visit")

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

(defvar lsp-python-ms-dotnet
(lsp-python-ms--find-dotnet)
"Full path to dotnet executable.

You only need to set this if dotnet is not on your path.")

(defvar lsp-python-ms-extra-paths '()
"A list of additional paths to search for python packages.

Expand All @@ -85,19 +71,76 @@ e.g, there are `python2' and `python3', both in system PATH,
and the default `python' links to python2,
set as `python3' to let ms-pyls use python 3 environments.")

(defun lsp-python-ms--find-server-executable ()
"Get path to the python language server executable."
(cond
((boundp 'lsp-python-ms-executable) lsp-python-ms-executable)
((executable-find "Microsoft.Python.LanguageServer"))
((executable-find "Microsoft.Python.LanguageServer.LanguageServer"))
((executable-find "Microsoft.Python.LanguageServer.exe"))
(t nil)))

(defvar lsp-python-ms-executable
(lsp-python-ms--find-server-executable)
(defvar lsp-python-ms-executable (concat lsp-python-ms-dir
"Microsoft.Python.LanguageServer"
(and (eq system-type 'windows-nt) ".exe"))
"Path to Microsoft.Python.LanguageServer.exe.")

(defun lsp-python-ms-latest-nupkg-url (&optional channel)
"Get the nupkg url of the latest Microsoft Python Language Server."
(let ((channel (or channel "stable")))
(unless (member channel '("stable" "beta" "daily"))
(error (format "Unknown channel: %s" channel)))
(with-current-buffer
(url-retrieve-synchronously
(format "https://pvsc.blob.core.windows.net/python-language-server-%s\
?restype=container&comp=list&prefix=Python-Language-Server-%s-x64"
channel
(cond ((eq system-type 'darwin) "osx")
((eq system-type 'gnu/linux) "linux")
((eq system-type 'windows-nt) "win")
(t (error (format "Unsupported system: %s" system-type))))))
(goto-char (point-min))
(re-search-forward "\n\n")
(pcase (xml-parse-region (point) (point-max))
(`((EnumerationResults
((ContainerName . ,_))
(Prefix nil ,_)
(Blobs nil . ,blobs)
(NextMarker nil)))
(cdar
(sort
(mapcar (lambda (blob)
(pcase blob
(`(Blob
nil
(Name nil ,_)
(Url nil ,url)
(Properties nil (Last-Modified nil ,last-modified) . ,_))
(cons (encode-time (parse-time-string last-modified)) url))))
blobs)
(lambda (t1 t2)
(time-less-p (car t2) (car t1))))))))))

(defun lsp-python-ms-setup (&optional forced)
"Downloading Microsoft Python Language Server to path specified.
With prefix, FORCED to redownload the server."
(interactive "P")
(unless (and (not forced)
(file-exists-p lsp-python-ms-executable))
(let ((temp-file (make-temp-file "mspyls" nil ".zip"))
(unzip-script (cond ((executable-find "unzip")
"bash -c 'mkdir -p %2$s && unzip -qq %1$s -d %2$s'")
((executable-find "powershell")
"powershell -noprofile -noninteractive \
-nologo -ex bypass Expand-Archive -path '%s' -dest '%s'")
(t nil))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may consider asking the user to manually unzip the file in case there is no powershell on this PC

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The powershell is built-in since Windows 7. And the behavior is same as dap-mode (dap-utils.el).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it seems to be ok to rely on that, thank you.

(message "Downloading Microsoft Python Language Server...")

(url-copy-file (lsp-python-ms-latest-nupkg-url) temp-file 'overwrite)
(if (file-exists-p lsp-python-ms-dir) (delete-directory lsp-python-ms-dir 'recursive))
(shell-command (format unzip-script temp-file lsp-python-ms-dir))
(if (file-exists-p lsp-python-ms-executable) (chmod lsp-python-ms-executable #o755))

(message "Downloaded Microsoft Python Language Server!"))))

(defun lsp-python-ms-update-server ()
"Update Microsoft Python Language Server."
(interactive)
(message "Server update started...")
(lsp-python-ms-setup t)
(message "Server update finished..."))

;; 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
Expand Down Expand Up @@ -137,7 +180,7 @@ lsp-workspace-root function that finds the current buffer's
workspace root. If nothing works, default to the current file's
directory"
(let ((workspace-root (if workspace (lsp--workspace-root workspace) (lsp-python-ms--workspace-root))))
(cl-destructuring-bind (pyver pysyspath)
(cl-destructuring-bind (pyver _pysyspath)
(lsp-python-ms--get-python-ver-and-syspath workspace-root)
`(:interpreter
(:properties (:InterpreterPath
Expand All @@ -160,7 +203,6 @@ directory"
:asyncStartup t
:typeStubSearchPaths ,(vector (concat lsp-python-ms-dir "Typeshed"))))))


(defun lsp-python-ms--filter-nbsp (str)
"Filter nbsp entities from STR."
(let ((rx " "))
Expand Down Expand Up @@ -201,26 +243,35 @@ other handlers. "

(defun lsp-python-ms--command-string ()
"Return the command to start the server."
(cond
((lsp-python-ms--find-server-executable))
((and (lsp-python-ms--find-dotnet) lsp-python-ms-dir)
(list (lsp-python-ms--find-dotnet)
(concat lsp-python-ms-dir "Microsoft.Python.LanguageServer.dll")))
(t (error "Could find Microsoft python language server"))))
;; Try to download server if it doesn't exists
(unless (file-exists-p lsp-python-ms-executable)
(lsp-python-ms-setup))

(if (file-exists-p lsp-python-ms-executable)
lsp-python-ms-executable
(error "Could find Microsoft python language server")))

(defgroup lsp-mspyls nil
"LSP support for Python, using Microsoft Python Language Server."
:group 'lsp-mode
:link '(url-link "https://github.com/emacs-lsp/lsp-python-ms"))

;; See https://github.com/microsoft/python-language-server for more diagnostics
(defcustom lsp-mspyls-errors ["unknown-parameter-name"
"undefined-variable"
"parameter-missing"
"positional-argument-after-keyword"
"too-many-function-arguments"]
"Microsoft Python LSP Error types.")
"Microsoft Python LSP Error types."
:group 'lsp-mspyls
:type 'vector)

(defcustom lsp-mspyls-warnings ["unresolved-import"
"parameter-already-specified"
"too-many-positional-arguments-before-star"]
"Microsoft Python LSP Warning types.")
"Microsoft Python LSP Warning types."
:group 'lsp-mspyls
:type 'vector)

(lsp-register-custom-settings '(("python.analysis.errors" lsp-mspyls-errors)))
(lsp-register-custom-settings '(("python.analysis.warnings" lsp-mspyls-warnings)))
Expand Down