336 lines
14 KiB
EmacsLisp
336 lines
14 KiB
EmacsLisp
|
|
;;; lsp-pyright.el --- Python LSP client using Pyright -*- lexical-binding: t; -*-
|
||
|
|
|
||
|
|
;; Copyright (C) 2020-2025 emacs-lsp maintainers
|
||
|
|
|
||
|
|
;; Author: Arif Rezai, Vincent Zhang, Andrew Christianson
|
||
|
|
;; Package-Version: 20260507.1742
|
||
|
|
;; Package-Revision: 187e08caee4e
|
||
|
|
;; Package-Requires: ((emacs "29.1") (lsp-mode "7.0") (dash "2.18.0") (ht "2.0"))
|
||
|
|
;; Homepage: https://github.com/emacs-lsp/lsp-pyright
|
||
|
|
;; Keywords: languages, tools, lsp
|
||
|
|
|
||
|
|
|
||
|
|
;; This file is not part of GNU Emacs
|
||
|
|
|
||
|
|
;; This file is free software; you can redistribute it and/or modify
|
||
|
|
;; it under the terms of the GNU General Public License as published by
|
||
|
|
;; the Free Software Foundation; either version 3, or (at your option)
|
||
|
|
;; any later version.
|
||
|
|
|
||
|
|
;; This program is distributed in the hope that it will be useful,
|
||
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
;; GNU General Public License for more details.
|
||
|
|
|
||
|
|
;; For a full copy of the GNU General Public License
|
||
|
|
;; see <https://www.gnu.org/licenses/>.
|
||
|
|
;;
|
||
|
|
;;; Commentary:
|
||
|
|
;;
|
||
|
|
;; Pyright language server.
|
||
|
|
;;
|
||
|
|
;;; Code:
|
||
|
|
|
||
|
|
(require 'lsp-mode)
|
||
|
|
(require 'dash)
|
||
|
|
(require 'ht)
|
||
|
|
(require 'cl-lib)
|
||
|
|
|
||
|
|
;; Group declaration
|
||
|
|
(defgroup lsp-pyright nil
|
||
|
|
"LSP support for python using the Pyright Language Server."
|
||
|
|
:group 'lsp-mode
|
||
|
|
:link '(url-link "https://github.com/microsoft/pyright")
|
||
|
|
:link '(url-link "https://github.com/DetachHead/basedpyright"))
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-langserver-command "pyright"
|
||
|
|
"Choose whether to use Pyright or the BasedPyright fork."
|
||
|
|
:type '(choice
|
||
|
|
(const "pyright")
|
||
|
|
(const "basedpyright"))
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-langserver-command-args '("--stdio")
|
||
|
|
"Command to start pyright-langserver."
|
||
|
|
:type '(repeat string)
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-disable-language-services nil
|
||
|
|
"Disables all language services except for \"hover\"."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-disable-organize-imports nil
|
||
|
|
"Disables the \"Organize Imports\" command."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-disable-tagged-hints nil
|
||
|
|
"Disables grayed out special hint diagnostics tags."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-diagnostic-mode "openFilesOnly"
|
||
|
|
"Determines pyright diagnostic mode.
|
||
|
|
Whether pyright analyzes (and reports errors for) all files
|
||
|
|
in the workspace, as indicated by the config file.
|
||
|
|
If this option is set to \"openFilesOnly\", pyright analyzes only open files."
|
||
|
|
:type '(choice
|
||
|
|
(const "openFilesOnly")
|
||
|
|
(const "workspace"))
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-type-checking-mode "standard"
|
||
|
|
"Specifies the default rule set to use.
|
||
|
|
|
||
|
|
\"off\" disables all type-checking rules, but Python syntax and semantic
|
||
|
|
errors are still reported. \"all\" reports all errors in basedpyright,
|
||
|
|
but is not supported by pyright. "
|
||
|
|
:type '(choice
|
||
|
|
(const :tag "Off" "off")
|
||
|
|
(const :tag "Basic" "basic")
|
||
|
|
(const :tag "Standard" "standard")
|
||
|
|
(const :tag "Strict" "strict")
|
||
|
|
(const :tag "All (basedpyright only)" "all"))
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-log-level "info"
|
||
|
|
"Determines the default log level used by pyright.
|
||
|
|
This can be overridden in the configuration file."
|
||
|
|
:type '(choice
|
||
|
|
(const "error")
|
||
|
|
(const "warning")
|
||
|
|
(const "info")
|
||
|
|
(const "trace"))
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-auto-search-paths t
|
||
|
|
"Determines whether pyright automatically adds common search paths.
|
||
|
|
i.e: Paths like \"src\" if there are no execution environments defined in the
|
||
|
|
config file."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-extra-paths []
|
||
|
|
"Paths to add to the default execution environment extra paths.
|
||
|
|
If there are no execution environments defined in the config file."
|
||
|
|
:type 'lsp-string-vector
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
(make-variable-buffer-local 'lsp-pyright-extra-paths)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-auto-import-completions t
|
||
|
|
"Determines whether pyright offers auto-import completions."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-venv-path nil
|
||
|
|
"Path to folder with subdirectories that contain virtual environments.
|
||
|
|
Virtual Envs specified in pyrightconfig.json will be looked up in this path."
|
||
|
|
:type '(choice (const :tag "None" nil) file)
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-venv-directory nil
|
||
|
|
"Folder with subdirectories that contain virtual environments.
|
||
|
|
Virtual Envs specified in pyrightconfig.json will be looked up in this path."
|
||
|
|
:type '(choice (const :tag "None" nil) directory)
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-multi-root t
|
||
|
|
"If non nil, lsp-pyright will be started in multi-root mode."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-python-executable-cmd "python"
|
||
|
|
"Command to specify the Python command for pyright.
|
||
|
|
Similar to the `python-shell-interpreter', but used only with mspyls.
|
||
|
|
Useful when there are multiple python versions in system.
|
||
|
|
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."
|
||
|
|
:type 'string
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-prefer-remote-env t
|
||
|
|
"If non nil, lsp-pyright will prefer remote python environment.
|
||
|
|
Only available in Emacs 27 and above."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-python-search-functions
|
||
|
|
'(lsp-pyright--locate-python-venv
|
||
|
|
lsp-pyright--locate-python-python)
|
||
|
|
"List of functions to search for python executable."
|
||
|
|
:type 'list
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-diagnostic-severity-overrides '()
|
||
|
|
"Overrides certain rules severity leve l"
|
||
|
|
:group 'lsp-pyright
|
||
|
|
:type `(alist :key-type (string :tag "Rule Name")
|
||
|
|
:value-type (choice
|
||
|
|
(const :tag "No Error" "none")
|
||
|
|
(const :tag "Warning""warning")
|
||
|
|
(const :tag "Information" "information")
|
||
|
|
(const :tag "Error" "error")
|
||
|
|
(const :tag "An error is generated" t)
|
||
|
|
(const :tag "No error is generated" ,json-false))))
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-basedpyright-inlay-hints-variable-types t
|
||
|
|
"Whether to show inlay hints on assignments to variables.
|
||
|
|
|
||
|
|
Basedpyright only."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-basedpyright-inlay-hints-call-argument-names t
|
||
|
|
"Whether to show inlay hints on function arguments.
|
||
|
|
|
||
|
|
Basedpyright only."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-basedpyright-inlay-hints-function-return-types t
|
||
|
|
"Whether to show inlay hints on function return types.
|
||
|
|
|
||
|
|
Basedpyright only."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defcustom lsp-pyright-basedpyright-inlay-hints-generic-types nil
|
||
|
|
"Whether to show inlay hints on inferred generic types.
|
||
|
|
|
||
|
|
Basedpyright only."
|
||
|
|
:type 'boolean
|
||
|
|
:group 'lsp-pyright)
|
||
|
|
|
||
|
|
(defun lsp-pyright--locate-venv ()
|
||
|
|
"Look for virtual environments local to the workspace."
|
||
|
|
(or lsp-pyright-venv-path
|
||
|
|
(and lsp-pyright-venv-directory
|
||
|
|
(-when-let (venv-base-directory (locate-dominating-file default-directory lsp-pyright-venv-directory))
|
||
|
|
(concat venv-base-directory lsp-pyright-venv-directory)))
|
||
|
|
(-when-let (venv-base-directory (locate-dominating-file default-directory "venv/"))
|
||
|
|
(concat venv-base-directory "venv"))
|
||
|
|
(-when-let (venv-base-directory (locate-dominating-file default-directory ".venv/"))
|
||
|
|
(concat venv-base-directory ".venv"))))
|
||
|
|
|
||
|
|
(defun lsp-pyright--locate-python-venv ()
|
||
|
|
"Find a python executable based on the current virtual environment."
|
||
|
|
(executable-find (f-expand "bin/python" (lsp-pyright--locate-venv))))
|
||
|
|
|
||
|
|
(defun lsp-pyright--locate-python-python ()
|
||
|
|
"Find a python executable based on the version of python on the PATH."
|
||
|
|
(with-no-warnings
|
||
|
|
(if (>= emacs-major-version 27)
|
||
|
|
(executable-find lsp-pyright-python-executable-cmd lsp-pyright-prefer-remote-env)
|
||
|
|
(executable-find lsp-pyright-python-executable-cmd))))
|
||
|
|
|
||
|
|
(defun lsp-pyright-locate-python ()
|
||
|
|
"Find a python executable cmd for the workspace."
|
||
|
|
(cl-some #'funcall lsp-pyright-python-search-functions))
|
||
|
|
|
||
|
|
(defun lsp-pyright--begin-progress-callback (workspace &rest _)
|
||
|
|
"Log begin progress information.
|
||
|
|
Current LSP WORKSPACE should be passed in."
|
||
|
|
(when lsp-progress-via-spinner
|
||
|
|
(with-lsp-workspace workspace
|
||
|
|
(--each (lsp--workspace-buffers workspace)
|
||
|
|
(when (buffer-live-p it)
|
||
|
|
(with-current-buffer it
|
||
|
|
(lsp--spinner-start))))))
|
||
|
|
(lsp-log "Pyright language server is analyzing..."))
|
||
|
|
|
||
|
|
(defun lsp-pyright--report-progress-callback (_workspace params)
|
||
|
|
"Log report progress information.
|
||
|
|
First element of PARAMS will be passed into `lsp-log'."
|
||
|
|
(when (and (arrayp params) (> (length params) 0))
|
||
|
|
(lsp-log (aref params 0))))
|
||
|
|
|
||
|
|
(defun lsp-pyright--end-progress-callback (workspace &rest _)
|
||
|
|
"Log end progress information.
|
||
|
|
Current LSP WORKSPACE should be passed in."
|
||
|
|
(when lsp-progress-via-spinner
|
||
|
|
(with-lsp-workspace workspace
|
||
|
|
(--each (lsp--workspace-buffers workspace)
|
||
|
|
(when (buffer-live-p it)
|
||
|
|
(with-current-buffer it
|
||
|
|
(lsp--spinner-stop))))))
|
||
|
|
(lsp-log "Pyright language server is analyzing...done"))
|
||
|
|
|
||
|
|
(defun lsp-pyright-organize-imports ()
|
||
|
|
"Organize imports in current buffer."
|
||
|
|
(interactive)
|
||
|
|
(lsp-send-execute-command (concat lsp-pyright-langserver-command ".organizeimports")
|
||
|
|
(vector (concat "file://" (buffer-file-name)))))
|
||
|
|
|
||
|
|
(lsp-register-custom-settings
|
||
|
|
`((,(concat lsp-pyright-langserver-command ".disableLanguageServices") lsp-pyright-disable-language-services t)
|
||
|
|
(,(concat lsp-pyright-langserver-command ".disableOrganizeImports") lsp-pyright-disable-organize-imports t)
|
||
|
|
(,(concat lsp-pyright-langserver-command ".disableTaggedHints") lsp-pyright-disable-tagged-hints t)
|
||
|
|
(,(concat lsp-pyright-langserver-command ".typeCheckingMode") lsp-pyright-type-checking-mode)
|
||
|
|
(,(concat lsp-pyright-langserver-command ".analysis.diagnosticSeverityOverrides") (lambda () (ht-from-alist lsp-pyright-diagnostic-severity-overrides)))
|
||
|
|
("python.analysis.diagnosticSeverityOverrides" (lambda () (ht-from-alist lsp-pyright-diagnostic-severity-overrides)))
|
||
|
|
("basedpyright.analysis.inlayHints.variableTypes" lsp-pyright-basedpyright-inlay-hints-variable-types t)
|
||
|
|
("basedpyright.analysis.inlayHints.callArgumentNames" lsp-pyright-basedpyright-inlay-hints-call-argument-names t)
|
||
|
|
("basedpyright.analysis.inlayHints.functionReturnTypes" lsp-pyright-basedpyright-inlay-hints-function-return-types t)
|
||
|
|
("basedpyright.analysis.inlayHints.genericTypes" lsp-pyright-basedpyright-inlay-hints-generic-types t)
|
||
|
|
("python.analysis.typeCheckingMode" lsp-pyright-type-checking-mode)
|
||
|
|
("python.analysis.autoImportCompletions" lsp-pyright-auto-import-completions t)
|
||
|
|
("python.analysis.diagnosticMode" lsp-pyright-diagnostic-mode)
|
||
|
|
("python.analysis.logLevel" lsp-pyright-log-level)
|
||
|
|
("python.analysis.autoSearchPaths" lsp-pyright-auto-search-paths t)
|
||
|
|
("python.analysis.extraPaths" lsp-pyright-extra-paths)
|
||
|
|
("python.pythonPath" lsp-pyright-locate-python)
|
||
|
|
;; We need to send empty string, otherwise pyright-langserver fails with parse error
|
||
|
|
("python.venvPath" (lambda () (or lsp-pyright-venv-path "")))))
|
||
|
|
|
||
|
|
(lsp-dependency 'pyright
|
||
|
|
`(:system ,(concat lsp-pyright-langserver-command "-langserver"))
|
||
|
|
`(:npm :package ,lsp-pyright-langserver-command
|
||
|
|
:path ,(concat lsp-pyright-langserver-command "-langserver")))
|
||
|
|
|
||
|
|
(lsp-register-client
|
||
|
|
(make-lsp-client
|
||
|
|
:new-connection (lsp-stdio-connection (lambda ()
|
||
|
|
(cons (lsp-package-path 'pyright)
|
||
|
|
lsp-pyright-langserver-command-args)))
|
||
|
|
:major-modes '(python-mode python-ts-mode)
|
||
|
|
:server-id 'pyright
|
||
|
|
:multi-root lsp-pyright-multi-root
|
||
|
|
:priority 2
|
||
|
|
:initialized-fn (lambda (workspace)
|
||
|
|
(with-lsp-workspace workspace
|
||
|
|
;; we send empty settings initially, LSP server will ask for the
|
||
|
|
;; configuration of each workspace folder later separately
|
||
|
|
(lsp--set-configuration
|
||
|
|
(make-hash-table :test 'equal))))
|
||
|
|
:download-server-fn (lambda (_client callback error-callback _update?)
|
||
|
|
(lsp-package-ensure 'pyright callback error-callback))
|
||
|
|
:notification-handlers (lsp-ht ((concat lsp-pyright-langserver-command "/beginProgress") 'lsp-pyright--begin-progress-callback)
|
||
|
|
((concat lsp-pyright-langserver-command "/reportProgress") 'lsp-pyright--report-progress-callback)
|
||
|
|
((concat lsp-pyright-langserver-command "/endProgress") 'lsp-pyright--end-progress-callback))))
|
||
|
|
|
||
|
|
(lsp-register-client
|
||
|
|
(make-lsp-client
|
||
|
|
:new-connection
|
||
|
|
(lsp-tramp-connection (lambda ()
|
||
|
|
(cons (concat lsp-pyright-langserver-command "-langserver") lsp-pyright-langserver-command-args)))
|
||
|
|
:major-modes '(python-mode python-ts-mode)
|
||
|
|
:server-id 'pyright-remote
|
||
|
|
:multi-root lsp-pyright-multi-root
|
||
|
|
:remote? t
|
||
|
|
:priority 1
|
||
|
|
:initialized-fn (lambda (workspace)
|
||
|
|
(with-lsp-workspace workspace
|
||
|
|
;; we send empty settings initially, LSP server will ask for the
|
||
|
|
;; configuration of each workspace folder later separately
|
||
|
|
(lsp--set-configuration
|
||
|
|
(make-hash-table :test 'equal))))
|
||
|
|
:notification-handlers (lsp-ht ((concat lsp-pyright-langserver-command "/beginProgress") 'lsp-pyright--begin-progress-callback)
|
||
|
|
((concat lsp-pyright-langserver-command "/reportProgress") 'lsp-pyright--report-progress-callback)
|
||
|
|
((concat lsp-pyright-langserver-command "/endProgress") 'lsp-pyright--end-progress-callback))))
|
||
|
|
|
||
|
|
(provide 'lsp-pyright)
|
||
|
|
;;; lsp-pyright.el ends here
|