Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

emacs-lsp-booster doesn't work well with emacs-pet/pyvenv regarding python virtual environment #37

Open
ramsayleung opened this issue Jan 5, 2025 · 10 comments

Comments

@ramsayleung
Copy link

Hi, Thanks so much for this awsome project, it works well with all language servers along with eglot, except for python project with virtual environment in my case.

I used to manage my virual environment with https://github.com/jorgenschaefer/pyvenv, then switch to pet later.

But I've been struggled with eglot-booster-mode for a while, after I enable eglot-booster-mode with pet, eglot fails to recognize all dependencies installed in the virtual environment.

How to reproduce

  1. Be sure that pyright-language-server is installed and in your PATH
  2. Create a virtual environment and activate it, and install a package.
python3 -m venv .venv
source .venv/bin/activate
pip install python-dotenv
  1. Create a poc python file:
from dotenv import load_dotenv
load_dotenv()  # take environment variables
  1. Launch Emacs with following minimal init.el
;; Initialize package sources
(require 'package)

(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                         ("nongnu" . "https://elpa.nongnu.org/nongnu/")
                         ("melpa" . "https://melpa.org/packages/")))

(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))

(require 'use-package)

(use-package eglot
  :ensure t
  :config
  (add-hook 'python-base-mode-hook 'eglot-ensure))

(use-package pet
  :ensure t
  :config
  (add-hook 'python-base-mode-hook 'pet-mode -10))
(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(package-selected-packages '(eglot-booster))
 '(package-vc-selected-packages
   '((eglot-booster :vc-backend Git :url "https://github.com/jdtsmith/eglot-booster"))))
  1. Open the poc.py with (eglot-booster-mode) enabled, flymake couldn't resolve the dotenv package
    image

  2. Reopen the poc.py with (eglot-booster-mode) disabled, restart eglot or restart emacs, flymake manages to resolve the dotenv

image

Logs

  1. Eglot event buffer with eglot-booster-mode enabled:
[internal] Sat Jan  4 22:41:19 2025:
(:message "Running language server: emacs-lsp-booster --json-false-value :json-false -- /opt/homebrew/bin/pyright-langserver --stdio")
[client-request] (id:1) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
	  (:processId 94745 :rootPath "/tmp/reproduce-bug/" :rootUri "file:///private/tmp/reproduce-bug" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
																	      ())
		      :capabilities
		      (:workspace
		       (:applyEdit t :executeCommand
				   (:dynamicRegistration :json-false)
				   :workspaceEdit
				   (:documentChanges t)
				   :didChangeWatchedFiles
				   (:dynamicRegistration t)
				   :symbol
				   (:dynamicRegistration :json-false)
				   :configuration t :workspaceFolders t)
		       :textDocument
		       (:synchronization
			(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
			:completion
			(:dynamicRegistration :json-false :completionItem
					      (:snippetSupport :json-false :deprecatedSupport t :resolveSupport
							       (:properties
								["documentation" "details" "additionalTextEdits"])
							       :tagSupport
							       (:valueSet
								[1]))
					      :contextSupport t)
			:hover
			(:dynamicRegistration :json-false :contentFormat
					      ["plaintext"])
			:signatureHelp
			(:dynamicRegistration :json-false :signatureInformation
					      (:parameterInformation
					       (:labelOffsetSupport t)
					       :activeParameterSupport t))
			:references
			(:dynamicRegistration :json-false)
			:definition
			(:dynamicRegistration :json-false :linkSupport t)
			:declaration
			(:dynamicRegistration :json-false :linkSupport t)
			:implementation
			(:dynamicRegistration :json-false :linkSupport t)
			:typeDefinition
			(:dynamicRegistration :json-false :linkSupport t)
			:documentSymbol
			(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
					      (:valueSet
					       [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
			:documentHighlight
			(:dynamicRegistration :json-false)
			:codeAction
			(:dynamicRegistration :json-false :codeActionLiteralSupport
					      (:codeActionKind
					       (:valueSet
						["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
					      :isPreferredSupport t)
			:formatting
			(:dynamicRegistration :json-false)
			:rangeFormatting
			(:dynamicRegistration :json-false)
			:rename
			(:dynamicRegistration :json-false)
			:inlayHint
			(:dynamicRegistration :json-false)
			:publishDiagnostics
			(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
					     (:valueSet
					      [1 2])))
		       :window
		       (:workDoneProgress t)
		       :general
		       (:positionEncodings
			["utf-32" "utf-8" "utf-16"])
		       :experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
						   ()))
		      :workspaceFolders
		      [(:uri "file:///private/tmp/reproduce-bug" :name "/tmp/reproduce-bug/")]))
[stderr] [2025-01-05T06:41:19Z INFO  emacs_lsp_booster::app] Running server "/opt/homebrew/bin/pyright-langserver" "--stdio"
[stderr] [2025-01-05T06:41:19Z INFO  emacs_lsp_booster::app] Will convert server json to bytecode! bytecode options: BytecodeOptions { object_type: Plist, null_value: Nil, false_value: Keyword("json-false") }
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Pyright language server 1.1.391 starting" :type 3))
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Server root directory: file:///opt/homebrew/Cellar/pyright/1.1.391/libexec/lib/node_modules/pyright/dist" :type 3))
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Starting service instance \"/tmp/reproduce-bug/\"" :type 3))
[server-reply] (id:1) Sat Jan  4 22:41:19 2025:
(:id 1 :jsonrpc "2.0" :result
     (:capabilities
      (:callHierarchyProvider t :codeActionProvider
			      (:codeActionKinds
			       ["quickfix" "source.organizeImports"]
			       :workDoneProgress t)
			      :completionProvider
			      (:completionItem
			       (:labelDetailsSupport t)
			       :resolveProvider t :triggerCharacters
			       ["." "[" "\"" "'"]
			       :workDoneProgress t)
			      :declarationProvider
			      (:workDoneProgress t)
			      :definitionProvider
			      (:workDoneProgress t)
			      :documentHighlightProvider
			      (:workDoneProgress t)
			      :documentSymbolProvider
			      (:workDoneProgress t)
			      :executeCommandProvider
			      (:commands
			       []
			       :workDoneProgress t)
			      :hoverProvider
			      (:workDoneProgress t)
			      :referencesProvider
			      (:workDoneProgress t)
			      :renameProvider
			      (:prepareProvider t :workDoneProgress t)
			      :signatureHelpProvider
			      (:triggerCharacters
			       ["(" "," ")"]
			       :workDoneProgress t)
			      :textDocumentSync 2 :typeDefinitionProvider
			      (:workDoneProgress t)
			      :workspace
			      (:workspaceFolders
			       (:changeNotifications t :supported t))
			      :workspaceSymbolProvider
			      (:workDoneProgress t))))
[client-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
							    ()))
[client-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
	  (:textDocument
	   (:uri "file:///private/tmp/reproduce-bug/poc.py" :version 0 :languageId "python" :text "from dotenv import load_dotenv\n\nload_dotenv()  # take environment variables\n")))
[client-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
	  (:settings #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
				   ())))
[server-request] (id:0) Sat Jan  4 22:41:19 2025:
(:id 0 :jsonrpc "2.0" :method "client/registerCapability" :params
     (:registrations
      [(:id "d6fe5999-2c7e-4418-ad06-697614bbe76c" :method "workspace/didChangeWatchedFiles" :registerOptions
	    (:watchers
	     [(:globPattern "**/pyrightconfig.json" :kind 7)
	      (:globPattern "**" :kind 7)]))]))
[client-reply] (id:0) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 0 :result nil)
[server-request] (id:1) Sat Jan  4 22:41:19 2025:
(:id 1 :jsonrpc "2.0" :method "workspace/configuration" :params
     (:items
      [(:scopeUri "file:///private/tmp/reproduce-bug" :section "python")]))
[client-reply] (id:1) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 1 :result
	  [nil])
[server-request] (id:2) Sat Jan  4 22:41:19 2025:
(:id 2 :jsonrpc "2.0" :method "workspace/configuration" :params
     (:items
      [(:scopeUri "file:///private/tmp/reproduce-bug" :section "python.analysis")]))
[client-reply] (id:2) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 2 :result
	  [nil])
[server-request] (id:3) Sat Jan  4 22:41:19 2025:
(:id 3 :jsonrpc "2.0" :method "workspace/configuration" :params
     (:items
      [(:scopeUri "file:///private/tmp/reproduce-bug" :section "pyright")]))
[client-reply] (id:3) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 3 :result
	  [nil])
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "No include entries specified; assuming /private/tmp/reproduce-bug" :type 3))
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Auto-excluding **/node_modules" :type 3))
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Auto-excluding **/__pycache__" :type 3))
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Auto-excluding **/.*" :type 3))
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:message "Found 2 source files" :type 3))
[server-request] (id:4) Sat Jan  4 22:41:19 2025:
(:id 4 :jsonrpc "2.0" :method "client/registerCapability" :params
     (:registrations
      [(:id "5c6294c5-1e99-4060-906e-0d66e647288c" :method "workspace/didChangeWatchedFiles" :registerOptions
	    (:watchers
	     [(:globPattern "**/pyrightconfig.json" :kind 7)
	      (:globPattern "**" :kind 7)]))]))
[client-reply] (id:4) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 4 :result nil)
[server-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
	  (:diagnostics
	   [(:code "reportMissingImports" :codeDescription
		   (:href "https://github.com/microsoft/pyright/blob/main/docs/configuration.md#reportMissingImports")
		   :message "Import \"dotenv\" could not be resolved" :range
		   (:end
		    (:character 11 :line 0)
		    :start
		    (:character 5 :line 0))
		   :severity 1 :source "Pyright")]
	   :uri "file:///private/tmp/reproduce-bug/poc.py" :version 0))
[server-request] (id:5) Sat Jan  4 22:41:19 2025:
(:id 5 :jsonrpc "2.0" :method "client/unregisterCapability" :params
     (:unregisterations
      [(:id "d6fe5999-2c7e-4418-ad06-697614bbe76c" :method "workspace/didChangeWatchedFiles")]))
[client-reply] (id:5) Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :id 5 :result nil)
[client-request] (id:2) Sat Jan  4 22:41:35 2025:
(:jsonrpc "2.0" :id 2 :method "textDocument/signatureHelp" :params
	  (:textDocument
	   (:uri "file:///private/tmp/reproduce-bug/poc.py")
	   :position
	   (:line 0 :character 5)))
[client-request] (id:3) Sat Jan  4 22:41:35 2025:
(:jsonrpc "2.0" :id 3 :method "textDocument/hover" :params
	  (:textDocument
	   (:uri "file:///private/tmp/reproduce-bug/poc.py")
	   :position
	   (:line 0 :character 5)))
[client-request] (id:4) Sat Jan  4 22:41:35 2025:
(:jsonrpc "2.0" :id 4 :method "textDocument/documentHighlight" :params
	  (:textDocument
	   (:uri "file:///private/tmp/reproduce-bug/poc.py")
	   :position
	   (:line 0 :character 5)))
[server-reply] (id:2) Sat Jan  4 22:41:35 2025:
(:id 2 :jsonrpc "2.0" :result nil)
[server-reply] (id:3) Sat Jan  4 22:41:35 2025:
(:id 3 :jsonrpc "2.0" :result nil)
[server-reply] (id:4) Sat Jan  4 22:41:35 2025:
(:id 4 :jsonrpc "2.0" :result
     [(:kind 2 :range
	     (:end
	      (:character 11 :line 0)
	      :start
	      (:character 5 :line 0)))])

  1. Eglot event buffer with eglot-booster-mode disabled:
[internal] Sat Jan  4 22:43:04 2025:
(:message "Running language server: /opt/homebrew/bin/pyright-langserver --stdio")
[client-request] (id:1) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
	  (:processId 94745 :rootPath "/tmp/reproduce-bug/" :rootUri "file:///private/tmp/reproduce-bug" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
																	      ())
		      :capabilities
		      (:workspace
		       (:applyEdit t :executeCommand
				   (:dynamicRegistration :json-false)
				   :workspaceEdit
				   (:documentChanges t)
				   :didChangeWatchedFiles
				   (:dynamicRegistration t)
				   :symbol
				   (:dynamicRegistration :json-false)
				   :configuration t :workspaceFolders t)
		       :textDocument
		       (:synchronization
			(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
			:completion
			(:dynamicRegistration :json-false :completionItem
					      (:snippetSupport :json-false :deprecatedSupport t :resolveSupport
							       (:properties
								["documentation" "details" "additionalTextEdits"])
							       :tagSupport
							       (:valueSet
								[1]))
					      :contextSupport t)
			:hover
			(:dynamicRegistration :json-false :contentFormat
					      ["plaintext"])
			:signatureHelp
			(:dynamicRegistration :json-false :signatureInformation
					      (:parameterInformation
					       (:labelOffsetSupport t)
					       :activeParameterSupport t))
			:references
			(:dynamicRegistration :json-false)
			:definition
			(:dynamicRegistration :json-false :linkSupport t)
			:declaration
			(:dynamicRegistration :json-false :linkSupport t)
			:implementation
			(:dynamicRegistration :json-false :linkSupport t)
			:typeDefinition
			(:dynamicRegistration :json-false :linkSupport t)
			:documentSymbol
			(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
					      (:valueSet
					       [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
			:documentHighlight
			(:dynamicRegistration :json-false)
			:codeAction
			(:dynamicRegistration :json-false :codeActionLiteralSupport
					      (:codeActionKind
					       (:valueSet
						["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
					      :isPreferredSupport t)
			:formatting
			(:dynamicRegistration :json-false)
			:rangeFormatting
			(:dynamicRegistration :json-false)
			:rename
			(:dynamicRegistration :json-false)
			:inlayHint
			(:dynamicRegistration :json-false)
			:publishDiagnostics
			(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
					     (:valueSet
					      [1 2])))
		       :window
		       (:workDoneProgress t)
		       :general
		       (:positionEncodings
			["utf-32" "utf-8" "utf-16"])
		       :experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
						   ()))
		      :workspaceFolders
		      [(:uri "file:///private/tmp/reproduce-bug" :name "/tmp/reproduce-bug/")]))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Pyright language server 1.1.391 starting"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Server root directory: file:///opt/homebrew/Cellar/pyright/1.1.391/libexec/lib/node_modules/pyright/dist"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Starting service instance \"/tmp/reproduce-bug/\""))
[server-reply] (id:1) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 1 :result
	  (:capabilities
	   (:textDocumentSync 2 :definitionProvider
			      (:workDoneProgress t)
			      :declarationProvider
			      (:workDoneProgress t)
			      :typeDefinitionProvider
			      (:workDoneProgress t)
			      :referencesProvider
			      (:workDoneProgress t)
			      :documentSymbolProvider
			      (:workDoneProgress t)
			      :workspaceSymbolProvider
			      (:workDoneProgress t)
			      :hoverProvider
			      (:workDoneProgress t)
			      :documentHighlightProvider
			      (:workDoneProgress t)
			      :renameProvider
			      (:prepareProvider t :workDoneProgress t)
			      :completionProvider
			      (:triggerCharacters
			       ["." "[" "\"" "'"]
			       :resolveProvider t :workDoneProgress t :completionItem
			       (:labelDetailsSupport t))
			      :signatureHelpProvider
			      (:triggerCharacters
			       ["(" "," ")"]
			       :workDoneProgress t)
			      :codeActionProvider
			      (:codeActionKinds
			       ["quickfix" "source.organizeImports"]
			       :workDoneProgress t)
			      :executeCommandProvider
			      (:commands
			       []
			       :workDoneProgress t)
			      :callHierarchyProvider t :workspace
			      (:workspaceFolders
			       (:supported t :changeNotifications t)))))
[client-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
							    ()))
[client-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
	  (:textDocument
	   (:uri "file:///private/tmp/reproduce-bug/poc.py" :version 0 :languageId "python" :text "from dotenv import load_dotenv\n\nload_dotenv()  # take environment variables\n")))
[client-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
	  (:settings
	   (:python
	    (:pythonPath "/tmp/reproduce-bug/.venv/bin/python" :venvPath "/tmp/reproduce-bug/.venv/"))))
[server-request] (id:0) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 0 :method "client/registerCapability" :params
	  (:registrations
	   [(:id "b4784d21-9050-40f1-b613-49b3ef9851f6" :method "workspace/didChangeWatchedFiles" :registerOptions
		 (:watchers
		  [(:globPattern "**/pyrightconfig.json" :kind 7)
		   (:globPattern "**" :kind 7)]))]))
[client-reply] (id:0) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 0 :result nil)
[server-request] (id:1) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 1 :method "workspace/configuration" :params
	  (:items
	   [(:scopeUri "file:///private/tmp/reproduce-bug" :section "python")]))
[client-reply] (id:1) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 1 :result
	  [(:pythonPath "/tmp/reproduce-bug/.venv/bin/python" :venvPath "/tmp/reproduce-bug/.venv/")])
[server-request] (id:2) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 2 :method "workspace/configuration" :params
	  (:items
	   [(:scopeUri "file:///private/tmp/reproduce-bug" :section "python.analysis")]))
[client-reply] (id:2) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 2 :result
	  [nil])
[server-request] (id:3) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 3 :method "workspace/configuration" :params
	  (:items
	   [(:scopeUri "file:///private/tmp/reproduce-bug" :section "pyright")]))
[client-reply] (id:3) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 3 :result
	  [nil])
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Setting pythonPath for service \"/tmp/reproduce-bug/\": \"/tmp/reproduce-bug/.venv/bin/python\""))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "No include entries specified; assuming /private/tmp/reproduce-bug"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Auto-excluding **/node_modules"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Auto-excluding **/__pycache__"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Auto-excluding **/.*"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Assuming Python version 3.13.1.final.0"))
[server-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "window/logMessage" :params
	  (:type 3 :message "Found 2 source files"))
[server-request] (id:4) Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :id 4 :method "client/registerCapability" :params
	  (:registrations
	   [(:id "fb09fcb6-18dc-4e28-864a-5b5cfe0fcb89" :method "workspace/didChangeWatchedFiles" :registerOptions
		 (:watchers
		  [(:globPattern "**/pyrightconfig.json" :kind 7)
		   (:globPattern "**" :kind 7)]))]))
[client-reply] (id:4) Sat Jan  4 22:43:05 2025:
(:jsonrpc "2.0" :id 4 :result nil)
[server-notification] Sat Jan  4 22:43:05 2025:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
	  (:uri "file:///private/tmp/reproduce-bug/poc.py" :version 0 :diagnostics
		[]))
[server-request] (id:5) Sat Jan  4 22:43:05 2025:
(:jsonrpc "2.0" :id 5 :method "client/unregisterCapability" :params
	  (:unregisterations
	   [(:id "b4784d21-9050-40f1-b613-49b3ef9851f6" :method "workspace/didChangeWatchedFiles")]))
[client-reply] (id:5) Sat Jan  4 22:43:05 2025:
(:jsonrpc "2.0" :id 5 :result nil)

@blahgeek
Copy link
Owner

blahgeek commented Jan 7, 2025

Thanks for the very detailed report.

Going though your log, I think the different may come from this:

[client-notification] Sat Jan  4 22:41:19 2025:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
	  (:settings #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
				   ())))

vs

[client-notification] Sat Jan  4 22:43:04 2025:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
	  (:settings
	   (:python
	    (:pythonPath "/tmp/reproduce-bug/.venv/bin/python" :venvPath "/tmp/reproduce-bug/.venv/"))))

This client notification sends the virtual env path to the server. If this step goes wrong, it may explain what you experienced. According to the log, when eglot-booster-mode is enabled, the client sends the settings as a hashtable instead of a plist.

I don't think the emacs-lsp-booster process have something to do with this, since it simply forwards the content as-is in the client->server direction. Maybe it's related to some settings by eglot-booster-mode. @jdtsmith Any comment?

@jdtsmith
Copy link
Contributor

jdtsmith commented Jan 7, 2025

No, emacs-lsp-booster does not alter workspace settings at all. In fact I use this same LSP server with it, and have no problems. So this is probably a bug with pet. Looking at pet, it hard-codes the names of various LSP servers, and advises various eglot commands to look them up. With the wrapper, the server name starts with /path/to/emacs-lsp-booster....

pet looks a bit heavy handed for simply setting the correct python path in the eglot workspace. I simply configure a eglot-workspace-configuration function to do this.

@ramsayleung
Copy link
Author

Thanks for your comment, I've CCed this issue to the author of pet, @jdtsmith would you like to share your workflow with eglot-workspace-configuration for heads-up, so I could have another option to recognize the packages in virtual environment if pet is not compatible with lsp-booster.

@jdtsmith
Copy link
Contributor

jdtsmith commented Jan 7, 2025

In the end you just need to set eglot-workspace-configuration to a function that returns a plist. Or perhaps easier, just use a config file in your project (see this example).

@wyuenho
Copy link

wyuenho commented Jan 7, 2025

eglot-booster and pet are advising completely different functions, they should not interfere with each other. pet purposely does not modify eglot-workspace-configuration directly, but instead advises eglot--workspace-configuration-plist in case the user has configured eglot-workspace-configuration with plain executable names.

When an empty hash table is sent in eglot-signal-didChangeConfiguration, it could only mean that eglot--workspace-configuration-plist is returning a nil. So the most interesting questions are:

What do you see from M-x eglot-show-workspace-configuration and,

How exactly do you enable eglot-booster-mode?

@ramsayleung
Copy link
Author

Not special, magical step to enable eglot-booster-mode, just by following the steps in README of eglot-booster-mode:

  1. I just installed eglot-booster-mode via package-vc-install,
  2. then put into my init.el
(use-package eglot-booster
	:after eglot
	:config	(eglot-booster-mode))
  1. restart my Emacs.

What do you see from M-x eglot-show-workspace-configuration, just null:

image

@wyuenho
Copy link

wyuenho commented Jan 9, 2025

I can't reproduce this issue on Emacs 30. All 3 packages works totally fine together. Are you able to reprod the same issue using the latest eglot from Elpa? You appear to be using the bundled eglot in Emacs 29.

@ramsayleung
Copy link
Author

Thanks for your heads-up, after installing eglot via list-packages command, the error magically dispears, though I still have no idea what's going wrong.

image

@ramsayleung
Copy link
Author

I encountered this problem again even though I've upgraded eglot to the latest version, so I wrote an Elisp functions to prompt the location of venv and write it into the pyrightconfig.json:

  (defun ramsay/pyrightconfig-find-venv-directories (project-root)
	"Find potential virtual environment directories for a project in PROJECT-ROOT."
	(let* ((common-venv-names '(".venv" ".env" "venv" "env"))
           (common-venv-locations (list project-root))
           (potential-paths '()))
      
      ;; Check common locations in project root and home directory
      (dolist (location common-venv-locations)
		(dolist (name common-venv-names)
          (let ((full-path (expand-file-name name location)))
			(when (file-directory-p full-path)
              (push full-path potential-paths)))))
      
      ;; Return found paths
      (reverse potential-paths)))

  (defun ramsay/pyrightconfig-suggest ()
	"Interactively select a virtualenv and write pyrightconfig.json."
	(interactive)
	(let* ((project-root (or (vc-git-root default-directory)
							 default-directory))
           (venv-paths (ramsay/pyrightconfig-find-venv-directories project-root))
           (selected-venv
			(completing-read
			 "Select virtual environment: "
			 (append venv-paths
					 ;; Add option to specify custom path
					 '("[Custom path...]"))
			 nil t)))
      
      (if (string= selected-venv "[Custom path...]")
          ;; If custom path selected, call original function
          (call-interactively #'ramsay/pyrightconfig-write)
		;; Otherwise use selected path
		(ramsay/pyrightconfig-write selected-venv))))

  ;; Derived from https://robbmann.io/posts/emacs-eglot-pyrightconfig/
  (defun ramsay/pyrightconfig-write (virtualenv)
	"Write pyrightconfig.json for the given VIRTUALENV path."
	(interactive "DEnv: ")
	(let* ((venv-dir (tramp-file-local-name (file-truename virtualenv)))
           (venv-file-name (directory-file-name venv-dir))
           (venvPath (file-name-directory venv-file-name))
           (venv (file-name-base venv-file-name))
           (base-dir (vc-git-root default-directory))
           (out-file (expand-file-name "pyrightconfig.json" base-dir))
           (out-contents (json-encode (list :venvPath venvPath :venv venv))))
      (with-temp-file out-file (insert out-contents))))

https://github.com/ramsayleung/emacs.d/blob/master/lisp/init-programming.el#L97-L144

@jdtsmith
Copy link
Contributor

I do something like that too, but I have eglot send it to pyright "over the wire" to keep all config in emacs. Something like:

(defun my/eglot-workspace-config (server)
    "A workspace config for pyright/basedpyright.
Note that some settings do not nest as you'd expect.  Allows
dir-local special config variable `pyright-extra-paths' to be
added via `:extraPaths`.'"
    (let ((config (list :python.analysis ; N.B. eglot does not consider :settings nested!
			;; (list :diagnosticMode "openFilesOnly")
			(list :stubPath
			      (expand-file-name "~/code/python/stubs/common")
			      ;; :diagnosticMode "openFilesOnly"
			      )
			;; recommended is TMI IMO
			:basedpyright.analysis '(:typeCheckingMode "standard")))) 
      (hack-dir-local-variables-non-file-buffer)
      (when (bound-and-true-p pyright-extra-paths)
	(setf (plist-get (cadr config) :extraPaths)
	      (vconcat pyright-extra-paths)))
      (when-let ((venv (simple-venv-find)) ; this command is local to my config
		 ((not (eq venv 'none)))
		 (vp (file-name-directory venv))
		 (vn (file-name-nondirectory venv)))
	(nconc config
	       (list :python `(;; :venvPath ,vp :venv ,vn
			       :pythonPath ,(simple-venv-interpreter venv)))))
      config))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants