├── .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 | --------------------------------------------------------------------------------