├── .gitignore
├── images
└── logo.png
├── Cask
├── docs
└── stylesheets
│ └── extra.css
├── Makefile
├── .github
└── workflows
│ ├── test.yml
│ └── docs.yml
├── mkdocs.yml
├── test
└── windows-bootstrap.el
├── LICENSE
├── README.org
└── lsp-python-ms.el
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.elc
3 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emacs-lsp/lsp-python-ms/HEAD/images/logo.png
--------------------------------------------------------------------------------
/Cask:
--------------------------------------------------------------------------------
1 | (source gnu)
2 | (source melpa)
3 |
4 | (package-file "lsp-python-ms.el")
5 |
6 | (files "*.el")
7 |
8 | (development
9 | (depends-on "lsp-mode")
10 | (depends-on "projectile"))
11 |
--------------------------------------------------------------------------------
/docs/stylesheets/extra.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --md-primary-fg-color: #234C6D;
3 | --md-accent-fg-color: #F7DD73;
4 | --md-default-fg-color--light: #3776AB;
5 | }
6 |
7 | .md-footer {
8 | --md-default-fg-color: #234C6D;
9 | }
10 |
11 | .md-grid {
12 | max-width: 70rem;
13 | }
14 |
15 | .md-typeset a {
16 | color: #3776AB;
17 | }
18 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /usr/bin/env bash
2 |
3 | EMACS ?= emacs
4 | CASK ?= cask
5 |
6 | windows-ci: CASK=
7 | windows-ci: clean windows-compile
8 |
9 | windows-compile:
10 | @echo "Compiling..."
11 | @$(CASK) $(EMACS) -Q --batch \
12 | -l test/windows-bootstrap.el \
13 | -L . \
14 | --eval '(setq byte-compile-error-on-warn t)' \
15 | -f batch-byte-compile *.el
16 |
17 | clean:
18 | rm -rf .cask *.elc
19 |
20 | .PHONY: clean windows-compile
21 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | continue-on-error: ${{ matrix.experimental }}
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | emacs-version: [26.3, 27.2, 28.2, 29.1]
13 | experimental: [false]
14 | include:
15 | - emacs-version: snapshot
16 | experimental: true
17 |
18 | steps:
19 | - name: setenv
20 | run: echo "ACTIONS_ALLOW_UNSECURE_COMMANDS=true" >> $GITHUB_ENV
21 |
22 | - uses: actions/checkout@v1
23 |
24 | - uses: purcell/setup-emacs@master
25 | with:
26 | version: ${{ matrix.emacs-version }}
27 |
28 | - uses: conao3/setup-cask@master
29 | with:
30 | version: 0.8.6
31 |
32 | - name: install
33 | run: 'cask install'
34 |
35 | - name: build
36 | run: 'cask build'
37 |
38 | - name: package
39 | run: 'cask package'
40 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: LSP Python MS
2 |
3 | extra_css:
4 | - stylesheets/extra.css
5 |
6 | theme:
7 | name: material
8 | logo: images/logo.png
9 | favicon: images/logo.png
10 | icon:
11 | repo: fontawesome/brands/github
12 |
13 | extra:
14 | social:
15 | - icon: fontawesome/brands/github-alt
16 | link: https://github.com/emacs-lsp
17 | - icon: fontawesome/brands/twitter
18 | link: https://twitter.com/yonchovski
19 | - icon: fontawesome/brands/gitter
20 | link: https://gitter.im/emacs-lsp/lsp-mode
21 |
22 | repo_name: emacs-lsp/lsp-python-ms
23 | repo_url: https://github.com/emacs-lsp/lsp-python-ms
24 |
25 | markdown_extensions:
26 | - pymdownx.superfences
27 | - pymdownx.emoji:
28 | emoji_index: !!python/name:materialx.emoji.twemoji
29 | emoji_generator: !!python/name:materialx.emoji.to_svg
30 | - codehilite
31 | - toc:
32 | permalink: '#'
33 |
34 | plugins:
35 | - search
36 | - awesome-pages
37 | - git-revision-date-localized
38 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v2
14 |
15 | - uses: purcell/setup-emacs@master
16 | with:
17 | version: 27.1
18 |
19 | - uses: conao3/setup-cask@master
20 | with:
21 | version: 0.8.4
22 |
23 | - name: README.org -> README.md
24 | uses: docker://pandoc/core:2.9
25 | with:
26 | args: -s README.org -t gfm -o docs/README.md
27 |
28 | - name: MkDocs
29 | run: |
30 | cp -rf images docs
31 | docker login docker.pkg.github.com --username $GITHUB_ACTOR --password ${{ secrets.GITHUB_TOKEN }}
32 | docker run --rm -v ${PWD}:/docs docker.pkg.github.com/emacs-lsp/docs-image/docs-image -- build
33 |
34 | - name: Deploy
35 | uses: peaceiris/actions-gh-pages@v3
36 | with:
37 | github_token: ${{ secrets.GITHUB_TOKEN }}
38 | publish_dir: ./site
39 |
--------------------------------------------------------------------------------
/test/windows-bootstrap.el:
--------------------------------------------------------------------------------
1 | ;;; windows-bootstrap.el --- Windows test bootstrap -*- lexical-binding: t; -*-
2 | ;;
3 | ;; Copyright (C) 2020-2021 emacs-lsp maintainers
4 | ;;
5 | ;; This program is free software; you can redistribute it and/or modify
6 | ;; it under the terms of the GNU General Public License as published by
7 | ;; the Free Software Foundation, either version 3 of the License, or
8 | ;; (at your option) any later version.
9 |
10 | ;; This program is distributed in the hope that it will be useful,
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ;; GNU General Public License for more details.
14 |
15 | ;; You should have received a copy of the GNU General Public License
16 | ;; along with this program. If not, see .
17 | ;;
18 | ;;; Commentary:
19 | ;;
20 | ;; Windows test bootstrap
21 | ;;
22 | ;;; Code:
23 |
24 | (require 'package)
25 |
26 | (let* ((package-archives '(("melpa" . "https://melpa.org/packages/")
27 | ("gnu" . "http://elpa.gnu.org/packages/")))
28 | (pkgs '(lsp-mode projectile project)))
29 | (package-initialize)
30 | (package-refresh-contents)
31 |
32 | (mapc (lambda (pkg)
33 | (unless (package-installed-p pkg)
34 | (package-install pkg)))
35 | pkgs)
36 |
37 | (add-hook 'kill-emacs-hook
38 | `(lambda () (delete-directory ,user-emacs-directory t))))
39 |
40 | ;;; windows-bootstrap.el ends here
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2018-2019, Charl Botha
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | [[https://github.com/emacs-lsp/lsp-python-ms/actions][https://github.com/emacs-lsp/lsp-python-ms/workflows/CI/badge.svg?branch=master]]
2 | [[http://img.shields.io/:License-BSD3-blue.svg]]
3 | [[https://melpa.org/#/lsp-python-ms][https://melpa.org/packages/lsp-python-ms-badge.svg]]
4 | [[https://stable.melpa.org/#/lsp-python-ms][https://stable.melpa.org/packages/lsp-python-ms-badge.svg]]
5 |
6 | =lsp-mode= client leveraging Microsoft's [[https://github.com/Microsoft/python-language-server][python-language-server]].
7 |
8 | This project is marked as DEPRECATED, since [Microsoft's Python Lang Server](https://github.com/microsoft/python-language-server) has already been archived and is replaced by [pyright](https://github.com/microsoft/pyright). The successor is [lsp-pyright](https://github.com/emacs-lsp/lsp-pyright).
9 |
10 | * Installation
11 |
12 | Include ~lsp-python-ms~ in the configuration file:
13 | #+BEGIN_SRC emacs-lisp
14 | (require 'lsp-python-ms)
15 | (setq lsp-python-ms-auto-install-server t)
16 | (add-hook 'python-mode-hook #'lsp) ; or lsp-deferred
17 | #+END_SRC
18 |
19 | A minimal ~use-package~ initialization might be:
20 | #+BEGIN_SRC elisp
21 | (use-package lsp-python-ms
22 | :ensure t
23 | :init (setq lsp-python-ms-auto-install-server t)
24 | :hook (python-mode . (lambda ()
25 | (require 'lsp-python-ms)
26 | (lsp)))) ; or lsp-deferred
27 | #+END_SRC
28 |
29 | ** Installing the executable
30 |
31 | *** NixOS
32 |
33 | Building the executable manually is difficult on NixOS, but it can be easily installed via the package manager.
34 | At the time of writing (May 2020), it is not available on the 20.03 release, but can be acquired on the unstable branch.
35 |
36 | #+begin_src bash
37 | nix-channel --add https://nixos.org/channels/nixos-unstable nixos
38 | nix-channel --update nixos
39 | #+end_src
40 |
41 | You can then install the executable by running ~nix-env -iA nixpkgs.python-language-server~
42 | or by adding ~nixpkgs.python-language-server~ to your ~configuration.nix~ and creating a new generation.
43 | Note that ~python37Packages.python-language-server~ refers to Palintir's language server, not Microsoft's.
44 |
45 | Finally, ensure that Emacs knows where to find the executable by setting ~lsp-python-ms-executable~.
46 |
47 | #+begin_src elisp
48 | (use-package lsp-python-ms
49 | :ensure t
50 | :hook (python-mode . (lambda ()
51 | (require 'lsp-python-ms)
52 | (lsp)))
53 | :init
54 | (setq lsp-python-ms-executable (executable-find "python-language-server")))
55 |
56 | #+end_src
57 |
58 | *** Most other distros
59 |
60 | Normally the [[https://github.com/Microsoft/python-language-server][python-language-server]] will be downloaded automatically if it doesn't
61 | exist while opening the python scripts.
62 |
63 | If you have troubles to download the package, you can also build the server yourselves.
64 |
65 | 1. Install [[https://www.microsoft.com/net/download][dotnet-sdk]]
66 | 2. Clone and build [[https://github.com/Microsoft/python-language-server][python-language-server]]:
67 | #+BEGIN_SRC bash
68 | git clone https://github.com/Microsoft/python-language-server.git
69 | cd python-language-server/src/LanguageServer/Impl
70 | dotnet publish -c Release -r osx-x64 # mac
71 | #+END_SRC
72 |
73 | Change the ~-r~ flag according to your architecture and operating system.
74 | See Microsoft's [[https://docs.microsoft.com/en-us/dotnet/core/rid-catalog][Runtime ID Catalog]] for the right value for your system.
75 |
76 | Then make the binary executable.
77 | #+BEGIN_SRC bash
78 | chmod a+x $(git rev-parse --show-toplevel)/output/bin/Release/osx-x64/publish/Microsoft.Python.LanguageServer
79 | #+END_SRC
80 |
81 | NOTE: on some systems (for example, Fedora), the executable comes out as
82 | ~Microsoft.Python.LanguageServer.LanguageServer~.
83 |
84 | 3. Set executable to ~lsp-python-ms-executable~.
85 |
86 | #+BEGIN_SRC elisp
87 | ;; for executable of language server, if it's not symlinked on your PATH
88 | (setq lsp-python-ms-executable
89 | "~/python-language-server/output/bin/Release/osx-x64/publish/Microsoft.Python.LanguageServer")
90 | #+END_SRC
91 |
92 | For development, you might find it useful to run =cask install=.
93 |
94 | * FAQ
95 |
96 | 1. Unresolved import warnings
97 |
98 | Set workspace root of `lsp-mode` properly, and add the extra directories to =lsp-python-ms-extra-paths= or =PYTHONPATH=.
99 | Refer to [[https://github.com/microsoft/python-language-server/blob/master/TROUBLESHOOTING.md#unresolved-import-warnings][Troubleshooting - Unresolved import warnings]] and [[https://github.com/emacs-lsp/lsp-python-ms/issues/96][#96]].
100 |
101 | 2. Autocompletion doesn't work
102 |
103 | The folder may have huge folders and files, and the server takes a long time to index them. So please DO NOT put huge files in the project/workspace folder.
104 |
105 | 3. Set path of the Python executable for each project/workspace
106 |
107 | Set the variable =lsp-python-ms-python-executable= before the `lsp-mode` being loaded.
108 |
109 | First, add =hack-local-variables-hook= in `init.el` to achieve loading `lsp-mode` after the `.dir-locals.el` file of each project/workspace being loaded.
110 |
111 | #+BEGIN_SRC emacs-lisp
112 | (add-hook 'hack-local-variables-hook
113 | (lambda ()
114 | (when (derived-mode-p 'python-mode)
115 | (require 'lsp-python-ms)
116 | (lsp)))) ; or lsp-deferred
117 | #+END_SRC
118 |
119 | Second, create `.dir-locals.el` file in the root directory of project to specify the varibale =lsp-python-ms-python-executable= for the project/workspace.
120 |
121 | #+BEGIN_SRC emacs-lisp
122 | ((python-mode . ((lsp-python-ms-python-executable . "/.../bin/python"))))
123 | #+END_SRC
124 |
125 | * Credit
126 |
127 | 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 work there.
128 |
--------------------------------------------------------------------------------
/lsp-python-ms.el:
--------------------------------------------------------------------------------
1 | ;;; lsp-python-ms.el --- The lsp-mode client for Microsoft python-language-server -*- lexical-binding: t -*-
2 |
3 | ;; Author: Charl Botha
4 | ;; Maintainer: Andrew Christianson, Vincent Zhang
5 | ;; Version: 0.7.2
6 | ;; Package-Requires: ((emacs "25.1") (lsp-mode "6.1"))
7 | ;; Homepage: https://github.com/emacs-lsp/lsp-python-ms
8 | ;; Keywords: languages tools
9 |
10 |
11 | ;; This file is not part of GNU Emacs
12 |
13 | ;; This file is free software; you can redistribute it and/or modify
14 | ;; it under the terms of the GNU General Public License as published by
15 | ;; the Free Software Foundation; either version 3, or (at your option)
16 | ;; any later version.
17 |
18 | ;; This program is distributed in the hope that it will be useful,
19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 | ;; GNU General Public License for more details.
22 |
23 | ;; For a full copy of the GNU General Public License
24 | ;; see .
25 |
26 |
27 | ;;; Commentary:
28 |
29 | ;; from https://vxlabs.com/2018/11/19/configuring-emacs-lsp-mode-and-microsofts-visual-studio-code-python-language-server/
30 |
31 | ;;; Code:
32 | (require 'cl-lib)
33 | (require 'conda nil 'noerror)
34 | (require 'find-file-in-project nil 'noerror)
35 | (require 'json)
36 | (require 'lsp-mode)
37 | (require 'projectile nil 'noerror)
38 | (require 'project nil 'noerror)
39 |
40 |
41 | ;; Forward declare functions
42 | (declare-function conda-env-name-to-dir "ext:conda")
43 | (declare-function ffip-get-project-root-directory "ext:find-file-in-project")
44 |
45 | ;; Forward declare variable
46 | (defvar conda-env-executables-dir)
47 | (defvar lsp-render-markdown-markup-content)
48 |
49 | ;; Group declaration
50 | (defgroup lsp-python-ms nil
51 | "LSP support for python using the Microsoft Python Language Server."
52 | :group 'lsp-mode
53 | :link '(url-link "https://github.com/Microsoft/python-language-server"))
54 |
55 | (defcustom lsp-python-ms-dir (f-join lsp-server-install-dir "mspyls/")
56 | "The directory of the Microsoft Python Language Server."
57 | :type 'directory
58 | :group 'lsp-python-ms)
59 |
60 | ;; not used since ms-pyls 0.2.92+
61 | ;; see https://github.com/microsoft/vscode-python/blob/master/src/client/activation/languageServer/analysisOptions.ts#L93
62 | ;; (defcustom lsp-python-ms-cache-dir
63 | ;; (directory-file-name (locate-user-emacs-file ".lsp-python/"))
64 | ;; "Path to directory where the server will write cache files.
65 |
66 | ;; If this is nil, the language server will write cache files in a directory
67 | ;; sibling to the root of every project you visit")
68 |
69 | (defcustom lsp-python-ms-guess-env t
70 | "Should the language server guess the paths.
71 |
72 | If true, check for pyenv environment/version files, then conda
73 | environment files, then project-local virtual environments, then
74 | fall back to the python on the head of PATH. Otherwise, just use
75 | the python on the head of PATH."
76 | :type 'boolean
77 | :group 'lsp-python-ms)
78 |
79 | (defcustom lsp-python-ms-python-executable nil
80 | "Path to specify the Python executable for the Microsoft Python Language Server."
81 | :type '(choice (const :tag "None" nil) file)
82 | :group 'lsp-python-ms)
83 |
84 | (defcustom lsp-python-ms-extra-paths []
85 | "A list of additional paths to search for python packages.
86 |
87 | This should be a list of paths corresponding to additional python
88 | library directories you want to search for completions. Paths
89 | should be as they are (or would appear) in sys.path. Paths will
90 | be prepended to the search path, and so will shadow duplicate
91 | names in search paths returned by the interpreter."
92 | :type 'lsp-string-vector
93 | :group 'lsp-python-ms)
94 | (make-variable-buffer-local 'lsp-python-ms-extra-paths)
95 |
96 | (defcustom lsp-python-ms-python-executable-cmd "python"
97 | "Command to specify the Python command for the Microsoft Python Language Server.
98 |
99 | Similar to the `python-shell-interpreter', but used only with mspyls.
100 | Useful when there are multiple python versions in system.
101 | e.g, there are `python2' and `python3', both in system PATH,
102 | and the default `python' links to python2,
103 | set as `python3' to let ms-pyls use python 3 environments."
104 | :type 'string
105 | :group 'lsp-python-ms)
106 |
107 | (defcustom lsp-python-ms-prefer-remote-env t
108 | "If Non-nil, will prefer remote python environment.
109 | Only available in Emacs 27 and above."
110 | :type 'boolean
111 | :group 'lsp-python-ms)
112 |
113 | (defcustom lsp-python-ms-executable (concat lsp-python-ms-dir
114 | "Microsoft.Python.LanguageServer"
115 | (if (eq system-type 'windows-nt) ".exe" ""))
116 | "Path to the Microsoft Python LanguageServer binary."
117 | :type '(file :must-match t)
118 | :group 'lsp-python-ms)
119 |
120 | (defcustom lsp-python-ms-auto-install-server t
121 | "Install Microsoft Python Language Server automatically."
122 | :type 'boolean
123 | :group 'lsp-python-ms)
124 |
125 | (defcustom lsp-python-ms-nupkg-channel "stable"
126 | "The channel of nupkg for the Microsoft Python Language Server:
127 | stable, beta or daily."
128 | :type 'string
129 | :group 'lsp-python-ms)
130 |
131 | (defcustom lsp-python-ms-completion-add-brackets t
132 | "Whether to add brackets after completion of functions."
133 | :type 'boolean
134 | :group 'lsp-python-ms)
135 |
136 | ;; See https://github.com/microsoft/python-language-server/blob/master/src/Analysis/Ast/Impl/Definitions/AnalysisOptions.cs
137 | (defcustom lsp-python-ms-cache "None"
138 | "The cache level of analysis for Microsoft Python Language Server."
139 | :type '(choice
140 | (const "None")
141 | (const "System")
142 | (const "Library"))
143 | :group 'lsp-python-ms)
144 |
145 | ;; See https://github.com/microsoft/python-language-server for more diagnostics
146 | (defcustom lsp-python-ms-errors ["unknown-parameter-name"
147 | "undefined-variable"
148 | "parameter-missing"
149 | "positional-argument-after-keyword"
150 | "too-many-function-arguments"]
151 | "Microsoft Python Language Server Error types."
152 | :type 'lsp-string-vector
153 | :group 'lsp-python-ms)
154 |
155 | (defcustom lsp-python-ms-warnings ["unresolved-import"
156 | "parameter-already-specified"
157 | "too-many-positional-arguments-before-star"]
158 | "Microsoft Python Language Server Warning types."
159 | :type 'lsp-string-vector
160 | :group 'lsp-python-ms)
161 |
162 | (defcustom lsp-python-ms-information []
163 | "Microsoft Python Language Server Information types."
164 | :type 'lsp-string-vector
165 | :group 'lsp-python-ms)
166 |
167 | (defcustom lsp-python-ms-disabled []
168 | "Microsoft Python Language Server Disabled types."
169 | :type 'lsp-string-vector
170 | :group 'lsp-python-ms)
171 |
172 | (defcustom lsp-python-ms-parse-dot-env-enabled t
173 | "Automatically parse .env file in the project root if non-nil."
174 | :type 'boolean
175 | :group 'lsp-python-ms)
176 |
177 | (defcustom lsp-python-ms-base-url "https://pvsc.blob.core.windows.net"
178 | "The base url to get nupkg package.
179 | The alternative is `https://pvsc.azureedge.net'."
180 | :type 'string
181 | :group 'lsp-python-ms)
182 |
183 | (defcustom lsp-python-ms-log-level "Error"
184 | "Log Level definition."
185 | :type 'string
186 | :group 'lsp-python-ms
187 | :options (list "Trace"
188 | "Info"
189 | "Information"
190 | "Error"
191 | "Warning"))
192 |
193 | (defcustom lsp-python-ms-extra-major-modes '()
194 | "A list of additional major modes in which to activate.
195 |
196 | In addition to the `python-mode', you may wish the Microsoft Python
197 | Language Server to activate in other major modes. If so, list them
198 | here."
199 | :type 'list
200 | :group 'lsp-python-ms)
201 |
202 | (defun lsp-python-ms-latest-nupkg-url (&optional channel)
203 | "Get the nupkg url through CHANNEL from Microsoft Python Language Server."
204 | (let ((channel (or channel "stable")))
205 | (unless (member channel '("stable" "beta" "daily"))
206 | (user-error "Unknown channel: %s" channel))
207 | (with-current-buffer
208 | (url-retrieve-synchronously
209 | (format "%s/python-language-server-%s?restype=container&comp=list&prefix=Python-Language-Server-%s-x64"
210 | lsp-python-ms-base-url
211 | channel
212 | (cond ((eq system-type 'darwin) "osx")
213 | ((eq system-type 'gnu/linux) "linux")
214 | ((eq system-type 'windows-nt) "win")
215 | (t (user-error "Unsupported system: %s" system-type)))))
216 | (goto-char (point-min))
217 | (re-search-forward "\n\n")
218 | (pcase (xml-parse-region (point) (point-max))
219 | (`((EnumerationResults
220 | ((ContainerName . ,_))
221 | (Prefix nil ,_)
222 | (Blobs nil . ,blobs)
223 | (NextMarker nil)))
224 | (cdar
225 | (sort
226 | (mapcar (lambda (blob)
227 | (pcase blob
228 | (`(Blob
229 | nil
230 | (Name nil ,_)
231 | (Url nil ,url)
232 | (Properties nil (Last-Modified nil ,last-modified) . ,_))
233 | (cons (apply #'encode-time (parse-time-string last-modified)) url))))
234 | blobs)
235 | (lambda (t1 t2)
236 | (time-less-p (car t2) (car t1))))))))))
237 |
238 | (defun lsp-python-ms--install-server (_client callback error-callback update?)
239 | "Downloading Microsoft Python Language Server to the specified path."
240 | (unless (and (not update?)
241 | (f-exists? lsp-python-ms-executable))
242 | (let* ((temp-file (make-temp-file "mspyls" nil ".zip"))
243 | (install-dir (expand-file-name lsp-python-ms-dir))
244 | (unzip-script (cond ((executable-find "unzip")
245 | (format "mkdir -p %s && unzip -qq %s -d %s"
246 | install-dir temp-file install-dir))
247 | ((executable-find "powershell")
248 | (format "powershell -noprofile -noninteractive \
249 | -nologo -ex bypass Expand-Archive -path '%s' -dest '%s'" temp-file install-dir))
250 | (t (lsp--info "Unable to extract '%s' to '%s'! \
251 | Please extract manually." temp-file install-dir)))))
252 |
253 | (lsp--info "Downloading Microsoft Python Language Server...")
254 |
255 | (url-retrieve
256 | (lsp-python-ms-latest-nupkg-url lsp-python-ms-nupkg-channel)
257 | (lambda (_data)
258 | ;; Skip http header
259 | (re-search-forward "\r?\n\r?\n")
260 |
261 | ;; Save to the temp file
262 | (let ((coding-system-for-write 'binary))
263 | (write-region (point) (point-max) temp-file))
264 |
265 | (lsp--info "Downloading Microsoft Python Language Server...done")
266 |
267 | ;; Extract the archive
268 | (f-delete install-dir t)
269 |
270 | (lsp-async-start-process
271 | (lambda ()
272 | (when (f-exists? lsp-python-ms-executable)
273 | (lsp--info "Extracted Microsoft Python Language Server")
274 | ;; Make the binary executable
275 | (chmod lsp-python-ms-executable #o755)
276 | ;; Start LSP if need
277 | (and lsp-mode (lsp)))
278 | (funcall callback))
279 | error-callback
280 | (if (executable-find "unzip") "sh" "cmd")
281 | (if (executable-find "unzip") "-c" "/c")
282 | unzip-script))))))
283 |
284 | ;;;###autoload
285 | (defun lsp-python-ms-update-server ()
286 | "Update Microsoft Python Language Server.
287 |
288 | On Windows, if the server is running, the updating will fail.
289 | After stopping or killing the process, retry to update."
290 | (interactive)
291 | (lsp-python-ms--install-server nil #'ignore #'lsp--error t))
292 |
293 | (defun lsp-python-ms--venv-dir (dir)
294 | "Check if the directory contains a virtualenv."
295 | (let ((dirs (and dir (f-directories dir))))
296 | (car (seq-filter #'lsp-python-ms--venv-python dirs))))
297 |
298 | (defun lsp-python-ms--venv-python (dir)
299 | "Check if a directory is a virtualenv."
300 | (let* ((python? (and t (f-expand "bin/python" dir)))
301 | (python3? (and python? (f-expand "bin/python3" dir)))
302 | (python (and python3?
303 | (cond ((f-executable? python?) python?)
304 | ((f-executable? python3?) python3?)
305 | (t nil))))
306 | (not-system (and python
307 | (let ((dir-parent (f-parent dir)))
308 | (not (or (string-equal dir-parent (expand-file-name "~"))
309 | (string-equal dir-parent "/")))))))
310 | (and not-system python)))
311 |
312 | (defun lsp-python-ms--dominating-venv-python (&optional dir)
313 | "Look for directories that look like venvs."
314 | (when-let ((dominating-venv
315 | (or (locate-dominating-file (or dir default-directory) #'lsp-python-ms--venv-python)
316 | (lsp-python-ms--venv-dir (locate-dominating-file (or dir default-directory) #'lsp-python-ms--venv-dir)))))
317 | (lsp-python-ms--venv-python dominating-venv)))
318 |
319 | (defun lsp-python-ms--dominating-conda-python (&optional dir)
320 | "Locate dominating conda environment."
321 | (let* ((path (or dir default-directory))
322 | (yamls (and path
323 | '("environment.yml" "environment.yaml"
324 | "env.yml" "env.yaml" "dev-environment.yml"
325 | "dev-environment.yaml")))
326 | (dominating-yaml (and yamls
327 | (seq-map (lambda (file)
328 | (when (locate-dominating-file path file)
329 | (expand-file-name file
330 | (locate-dominating-file path file))))
331 | yamls)))
332 | (dominating-yaml-file (and dominating-yaml
333 | (car (seq-filter
334 | (lambda (file) file) dominating-yaml))))
335 | (dominating-conda-name (and dominating-yaml-file
336 | (fboundp 'conda--get-name-from-env-yml)
337 | (or (bound-and-true-p conda-env-current-name)
338 | (conda--get-name-from-env-yml dominating-yaml-file)))))
339 | (when dominating-conda-name
340 | (expand-file-name
341 | (file-name-nondirectory lsp-python-ms-python-executable-cmd)
342 | (expand-file-name conda-env-executables-dir
343 | (conda-env-name-to-dir dominating-conda-name))))))
344 |
345 | (defun lsp-python-ms--dominating-pyenv-python (&optional dir)
346 | "Locate dominating pyenv-managed python."
347 | (let ((dir (or dir default-directory)))
348 | (when (locate-dominating-file dir ".python-version")
349 | (string-trim (shell-command-to-string "pyenv which python")))))
350 |
351 | (defun lsp-python-ms--dominating-asdf-python (&optional dir)
352 | "Locate dominating asdf-managed python"
353 | (let ((dir (or dir default-directory)))
354 | (when (locate-dominating-file dir ".tool-versions")
355 | (string-trim (shell-command-to-string "asdf which python")))))
356 |
357 | (defun lsp-python-ms--valid-python (path)
358 | (and path (f-executable? path) path))
359 |
360 | (defun lsp-python-ms-locate-python (&optional dir)
361 | "Look for virtual environments local to the workspace."
362 | (let* ((pyenv-python (lsp-python-ms--dominating-pyenv-python dir))
363 | (asdf-python (lsp-python-ms--dominating-asdf-python dir))
364 | (venv-python (lsp-python-ms--dominating-venv-python dir))
365 | (conda-python (lsp-python-ms--dominating-conda-python dir))
366 | (sys-python
367 | (with-no-warnings
368 | (if (>= emacs-major-version 27)
369 | (executable-find lsp-python-ms-python-executable-cmd lsp-python-ms-prefer-remote-env)
370 | ;; This complains in Windows' Emacs 26.1, see #141
371 | (executable-find lsp-python-ms-python-executable-cmd)))))
372 | ;; pythons by preference: local pyenv version, local conda version
373 |
374 | (if lsp-python-ms-guess-env
375 | (cond ((lsp-python-ms--valid-python lsp-python-ms-python-executable))
376 | ((lsp-python-ms--valid-python venv-python))
377 | ((lsp-python-ms--valid-python asdf-python))
378 | ((lsp-python-ms--valid-python pyenv-python))
379 | ((lsp-python-ms--valid-python conda-python))
380 | ((lsp-python-ms--valid-python sys-python)))
381 | (cond ((lsp-python-ms--valid-python sys-python))))))
382 |
383 | ;; it's crucial that we send the correct Python version to MS PYLS,
384 | ;; else it returns no docs in many cases furthermore, we send the
385 | ;; current Python's (can be virtualenv) sys.path as searchPaths
386 | (defun lsp-python-ms--get-python-ver-and-syspath (&optional workspace-root)
387 | "Return list with pyver-string and list of python search paths.
388 |
389 | The WORKSPACE-ROOT will be prepended to the list of python search
390 | paths and then the entire list will be json-encoded."
391 | (let* ((python (and t (lsp-python-ms-locate-python)))
392 | (workspace-root (and python (or workspace-root ".")))
393 | (default-directory (and workspace-root workspace-root))
394 | (init (and default-directory
395 | "from __future__ import print_function; import sys; sys.path = list(filter(lambda p: p != '', sys.path)); import json;"))
396 | (ver (and init "v=(\"%s.%s\" % (sys.version_info[0], sys.version_info[1]));"))
397 | (sp (and ver (concat "sys.path.insert(0, '" workspace-root "'); p=sys.path;")))
398 | (ex (and sp "e=sys.executable;"))
399 | (val (and ex "print(json.dumps({\"version\":v,\"paths\":p,\"executable\":e}))")))
400 | (when val
401 | (with-temp-buffer
402 | (call-process python nil t nil "-c"
403 | (concat init ver sp ex val))
404 | (let* ((json-array-type 'vector)
405 | (json-key-type 'string)
406 | (json-object-type 'hash-table)
407 | (json-string (buffer-string))
408 | (json-hash (json-read-from-string json-string)))
409 | (list
410 | (gethash "version" json-hash)
411 | (gethash "paths" json-hash)
412 | (gethash "executable" json-hash)))))))
413 |
414 | (defun lsp-python-ms--workspace-root ()
415 | "Get the path of the root of the current workspace.
416 |
417 | Use `lsp-workspace-root', which is pressent in the \"new\"
418 | lsp-mode and works when there's an active session. Next try ffip
419 | or projectile, or just return `default-directory'."
420 | (cond
421 | ((fboundp #'lsp-workspace-root) (lsp-workspace-root))
422 | ((fboundp #'ffip-get-project-root-directory) (ffip-get-project-root-directory))
423 | ((fboundp #'projectile-project-root) (projectile-project-root))
424 | ((fboundp #'project-current) (when-let ((project (project-current)))
425 | (car (or (and (fboundp 'project-root) (project-root project))
426 | ;; Function `project-roots' is obsolete, by having
427 | ;; just to make compatible to older `project.el' package.
428 | (with-no-warnings (project-roots project))))))
429 | (t default-directory)))
430 |
431 | ;; I based most of this on the vs.code implementation:
432 | ;; https://github.com/microsoft/vscode-python/blob/master/src/client/activation/languageServer/analysisOptions.ts
433 | ;; (it still took quite a while to get right, but here we are!)
434 | (defun lsp-python-ms--extra-init-params (&optional workspace)
435 | "Return form describing parameters for language server.
436 |
437 | Old lsp will pass in a WORKSPACE, new lsp has a global
438 | lsp-workspace-root function that finds the current buffer's
439 | workspace root. If nothing works, default to the current file's
440 | directory"
441 | (let ((workspace-root (or (if workspace
442 | (lsp--workspace-root workspace)
443 | (lsp-python-ms--workspace-root))
444 | default-directory)))
445 | (when lsp-python-ms-parse-dot-env-enabled
446 | (lsp-python-ms--parse-dot-env workspace-root))
447 | (cl-destructuring-bind (pyver pysyspath pyintpath)
448 | (lsp-python-ms--get-python-ver-and-syspath workspace-root)
449 | `(:interpreter
450 | (:properties
451 | (:InterpreterPath ,pyintpath :UseDefaultDatabase t :Version ,pyver))
452 | ;; preferredFormat "markdown" or "plaintext"
453 | ;; experiment to find what works best -- over here mostly plaintext
454 | :displayOptions (:preferredFormat
455 | "markdown"
456 | :trimDocumentationLines :json-false
457 | :maxDocumentationLineLength 0
458 | :trimDocumentationText :json-false
459 | :maxDocumentationTextLength 0)
460 | :searchPaths ,(vconcat lsp-python-ms-extra-paths pysyspath)
461 | :analysisUpdates t
462 | :asyncStartup t
463 | :logLevel ,lsp-python-ms-log-level
464 | :typeStubSearchPaths ,(vector (expand-file-name (f-join lsp-python-ms-dir "Typeshed")))))))
465 |
466 | (defun lsp-python-ms--filter-nbsp (str)
467 | "Filter nbsp entities from STR."
468 | (let ((rx " "))
469 | (when (eq system-type 'windows-nt)
470 | (setq rx (concat rx "\\|\r")))
471 | (when str
472 | (replace-regexp-in-string rx " " str))))
473 |
474 | (defun lsp-python-ms--parse-dot-env (root &optional envvar)
475 | "Set environment variable (default PYTHONPATH) from .env file if this file exists in the project root."
476 | (let* ((envvar (or envvar "PYTHONPATH"))
477 | (file (f-join (file-name-as-directory root) ".env"))
478 | (rx (concat "^[:blank:]*" envvar "[:blank:]*=[:blank:]*"))
479 | val)
480 | (when (and (f-exists? file) (f-file? file) (f-readable? file))
481 | (with-temp-buffer
482 | (insert-file-contents file)
483 | (keep-lines rx (point-min) (point-max))
484 | (when (string-match (concat rx "\\(.*\\)") (buffer-string))
485 | (setq val (match-string 1 (buffer-string)))
486 | (unless (string-empty-p val)
487 | (setenv envvar val)))))))
488 |
489 | (defun lsp-python-ms--language-server-started-callback (_workspace _params)
490 | "Handle the python/languageServerStarted message.
491 |
492 | WORKSPACE is just used for logging and _PARAMS is unused."
493 | (lsp--info "Microsoft Python language server started"))
494 |
495 | ;; this gets called when we do lsp-describe-thing-at-point
496 | ;; see lsp-methods.el. As always, remove Microsoft's unwanted entities :(
497 | (setq lsp-render-markdown-markup-content #'lsp-python-ms--filter-nbsp)
498 |
499 | ;; lsp-ui-doc--extract gets called when hover docs are requested
500 | ;; as always, we have to remove Microsoft's unnecessary entities
501 | (advice-add 'lsp-ui-doc--extract
502 | :filter-return #'lsp-python-ms--filter-nbsp)
503 |
504 | ;; lsp-ui-sideline--format-info gets called when lsp-ui wants to show
505 | ;; hover info in the sideline again has to be removed
506 | (advice-add 'lsp-ui-sideline--format-info
507 | :filter-return #'lsp-python-ms--filter-nbsp)
508 |
509 | (defun lsp-python-ms--report-progress-callback (_workspace params)
510 | "Log progress information."
511 | (when (and (arrayp params) (> (length params) 0))
512 | (lsp-log (aref params 0))))
513 |
514 | (defun lsp-python-ms--begin-progress-callback (workspace &rest _)
515 | (with-lsp-workspace workspace
516 | (--each (lsp--workspace-buffers workspace)
517 | (when (buffer-live-p it)
518 | (with-current-buffer it
519 | (lsp--spinner-start)))))
520 | (lsp--info "Microsoft Python language server is analyzing..."))
521 |
522 | (defun lsp-python-ms--end-progress-callback (workspace &rest _)
523 | (with-lsp-workspace workspace
524 | (--each (lsp--workspace-buffers workspace)
525 | (when (buffer-live-p it)
526 | (with-current-buffer it
527 | (lsp--spinner-stop))))
528 | (lsp--info "Microsoft Python language server is analyzing...done")))
529 |
530 | (lsp-register-custom-settings
531 | `(("python.autoComplete.addBrackets" lsp-python-ms-completion-add-brackets t)
532 | ("python.analysis.cachingLevel" lsp-python-ms-cache)
533 | ("python.analysis.errors" lsp-python-ms-errors)
534 | ("python.analysis.warnings" lsp-python-ms-warnings)
535 | ("python.analysis.information" lsp-python-ms-information)
536 | ("python.analysis.disabled" lsp-python-ms-disabled)
537 | ("python.analysis.autoSearchPaths" (lambda () (<= (length lsp-python-ms-extra-paths) 0)) t)
538 | ("python.autoComplete.extraPaths" lsp-python-ms-extra-paths)))
539 |
540 | (dolist (mode lsp-python-ms-extra-major-modes)
541 | (add-to-list 'lsp-language-id-configuration `(,mode . "python")))
542 |
543 | (lsp-register-client
544 | (make-lsp-client
545 | :new-connection (lsp-stdio-connection (lambda () lsp-python-ms-executable)
546 | (lambda () (f-exists? lsp-python-ms-executable)))
547 | :major-modes (append '(python-mode) lsp-python-ms-extra-major-modes)
548 | :server-id 'mspyls
549 | :priority 1
550 | :initialization-options 'lsp-python-ms--extra-init-params
551 | :notification-handlers (lsp-ht ("python/languageServerStarted" 'lsp-python-ms--language-server-started-callback)
552 | ("telemetry/event" 'ignore)
553 | ("python/reportProgress" 'lsp-python-ms--report-progress-callback)
554 | ("python/beginProgress" 'lsp-python-ms--begin-progress-callback)
555 | ("python/endProgress" 'lsp-python-ms--end-progress-callback))
556 | :initialized-fn (lambda (workspace)
557 | (with-lsp-workspace workspace
558 | (lsp--set-configuration (lsp-configuration-section "python"))))
559 | :download-server-fn (lambda (client callback error-callback update?)
560 | (when lsp-python-ms-auto-install-server
561 | (lsp-python-ms--install-server client callback error-callback update?)))))
562 |
563 | (provide 'lsp-python-ms)
564 |
565 | ;;; lsp-python-ms.el ends here
566 |
--------------------------------------------------------------------------------