3
3
import json
4
4
import os
5
5
import re
6
- import shutil
7
- import weakref
8
- from dataclasses import dataclass
9
- from pathlib import Path
10
6
from typing import Any , cast
11
7
12
8
import jmespath
13
9
import sublime
10
+ import sublime_plugin
14
11
from LSP .plugin import ClientConfig , DottedDict , MarkdownLangMap , Response , WorkspaceFolder
15
12
from LSP .plugin .core .protocol import CompletionItem , Hover , SignatureHelp
16
13
from lsp_utils import NpmClientHandler
17
- from more_itertools import first_true
18
14
from sublime_lib import ResourcePath
19
15
20
16
from .constants import PACKAGE_NAME , SERVER_SETTING_DEV_ENVIRONMENT
21
17
from .dev_environment .helpers import get_dev_environment_handler
22
- from .log import log_error , log_info , log_warning
23
- from .template import load_string_template
24
- from .virtual_env .helpers import find_venv_by_finder_names , find_venv_by_python_executable
25
- from .virtual_env .venv_finder import BaseVenvInfo , get_finder_name_mapping
18
+ from .log import log_error , log_warning
19
+ from .utils_lsp import AbstractLspPythonPlugin , find_workspace_folder , update_view_status_bar_text , uri_to_file_path
20
+ from .virtual_env .helpers import find_venv_by_finder_names
26
21
27
- WindowId = int
28
22
23
+ class ViewEventListener (sublime_plugin .ViewEventListener ):
24
+ def on_activated (self ) -> None :
25
+ settings = self .view .settings ()
29
26
30
- @dataclass
31
- class WindowAttr :
32
- simple_python_executable : Path | None = None
33
- """The path to the Python executable found by the `PATH` env variable."""
34
- venv_info : BaseVenvInfo | None = None
35
- """The information of the virtual environment."""
27
+ if settings .get ("lsp_active" ):
28
+ update_view_status_bar_text (LspBasedpyrightPlugin , self .view )
36
29
37
- @property
38
- def preferred_python_executable (self ) -> Path | None :
39
- return self .venv_info .python_executable if self .venv_info else self .simple_python_executable
40
30
41
-
42
- class LspBasedpyrightPlugin (NpmClientHandler ):
31
+ class LspBasedpyrightPlugin (AbstractLspPythonPlugin , NpmClientHandler ):
43
32
package_name = PACKAGE_NAME
44
33
server_directory = "language-server"
45
34
server_binary_path = os .path .join (server_directory , "node_modules" , "basedpyright" , "langserver.index.js" )
46
35
47
- server_version = ""
48
- """The version of the language server."""
49
-
50
- window_attrs : weakref .WeakKeyDictionary [sublime .Window , WindowAttr ] = weakref .WeakKeyDictionary ()
51
- """Per-window attributes. I.e., per-session attributes."""
52
-
53
36
@classmethod
54
37
def required_node_version (cls ) -> str :
55
38
"""
@@ -83,8 +66,6 @@ def can_start(
83
66
) -> str | None :
84
67
if message := super ().can_start (window , initiating_view , workspace_folders , configuration ):
85
68
return message
86
-
87
- cls .window_attrs .setdefault (window , WindowAttr ())
88
69
return None
89
70
90
71
def on_settings_changed (self , settings : DottedDict ) -> None :
@@ -104,24 +85,6 @@ def on_settings_changed(self, settings: DottedDict) -> None:
104
85
except Exception as ex :
105
86
log_error (f'Failed to update extra paths for dev environment "{ dev_environment } ": { ex } ' )
106
87
107
- self .update_status_bar_text ()
108
-
109
- @classmethod
110
- def on_pre_start (
111
- cls ,
112
- window : sublime .Window ,
113
- initiating_view : sublime .View ,
114
- workspace_folders : list [WorkspaceFolder ],
115
- configuration : ClientConfig ,
116
- ) -> str | None :
117
- super ().on_pre_start (window , initiating_view , workspace_folders , configuration )
118
-
119
- cls .update_venv_info (configuration .settings , workspace_folders , window = window )
120
- if venv_info := cls .window_attrs [window ].venv_info :
121
- log_info (f"Using python executable: { venv_info .python_executable } " )
122
- configuration .settings .set ("python.pythonPath" , str (venv_info .python_executable ))
123
- return None
124
-
125
88
@classmethod
126
89
def install_or_update (cls ) -> None :
127
90
super ().install_or_update ()
@@ -156,6 +119,35 @@ def on_server_response_async(self, method: str, response: Response) -> None:
156
119
documentation ["value" ] = self .patch_markdown_content (documentation ["value" ])
157
120
return
158
121
122
+ def on_workspace_configuration (self , params : Any , configuration : dict [str , Any ]) -> dict [str , Any ]:
123
+ # provide detected venv information from the workspace folder
124
+ # note that `pyrightconfig.json` seems to be auto-prioritized by the server
125
+ if (
126
+ (session := self .weaksession ())
127
+ and (params ["section" ] == "python" )
128
+ and (scope_uri := params .get ("scopeUri" ))
129
+ and (file_path := uri_to_file_path (scope_uri ))
130
+ and (wf_path := find_workspace_folder (session .window , file_path ))
131
+ and (venv_strategies := session .config .settings .get ("venvStrategies" ))
132
+ and (venv_info := find_venv_by_finder_names (venv_strategies , project_dir = wf_path ))
133
+ ):
134
+ self .wf_attrs [wf_path ].venv_info = venv_info
135
+ # When ST just starts, server session hasn't been created yet.
136
+ # So `on_activated` can't add full information for the initial view and hence we handle it here.
137
+ if active_view := sublime .active_window ().active_view ():
138
+ update_view_status_bar_text (self .__class__ , active_view )
139
+
140
+ # modify configuration for the venv
141
+ site_packages_dir = str (venv_info .site_packages_dir )
142
+ conf_analysis : dict [str , Any ] = configuration .setdefault ("analysis" , {})
143
+ conf_analysis_extra_paths : list [str ] = conf_analysis .setdefault ("extraPaths" , [])
144
+ if site_packages_dir not in conf_analysis_extra_paths :
145
+ conf_analysis_extra_paths .insert (0 , site_packages_dir )
146
+ if not configuration .get ("pythonPath" ):
147
+ configuration ["pythonPath" ] = str (venv_info .python_executable )
148
+
149
+ return configuration
150
+
159
151
# -------------- #
160
152
# custom methods #
161
153
# -------------- #
@@ -173,32 +165,6 @@ def copy_overwrite_dirs(cls) -> None:
173
165
except OSError :
174
166
raise RuntimeError (f'Failed to copy overwrite dirs from "{ dir_src } " to "{ dir_dst } ".' )
175
167
176
- def update_status_bar_text (self , extra_variables : dict [str , Any ] | None = None ) -> None :
177
- if not (session := self .weaksession ()):
178
- return
179
-
180
- variables : dict [str , Any ] = {
181
- "server_version" : self .server_version ,
182
- }
183
-
184
- if venv_info := self .window_attrs [session .window ].venv_info :
185
- variables ["venv" ] = {
186
- "finder_name" : venv_info .meta .finder_name ,
187
- "python_version" : venv_info .python_version ,
188
- "venv_prompt" : venv_info .prompt ,
189
- }
190
-
191
- if extra_variables :
192
- variables .update (extra_variables )
193
-
194
- rendered_text = ""
195
- if template_text := str (session .config .settings .get ("statusText" ) or "" ):
196
- try :
197
- rendered_text = load_string_template (template_text ).render (variables )
198
- except Exception as e :
199
- log_warning (f'Invalid "statusText" template: { e } ' )
200
- session .set_config_status_async (rendered_text )
201
-
202
168
def patch_markdown_content (self , content : str ) -> str :
203
169
# Add another linebreak before horizontal rule following fenced code block
204
170
content = re .sub ("```\n ---" , "```\n \n ---" , content )
@@ -220,40 +186,3 @@ def patch_markdown_content(self, content: str) -> str:
220
186
def parse_server_version (cls ) -> str :
221
187
lock_file_content = sublime .load_resource (f"Packages/{ PACKAGE_NAME } /language-server/package-lock.json" )
222
188
return jmespath .search ("dependencies.basedpyright.version" , json .loads (lock_file_content )) or ""
223
-
224
- @classmethod
225
- def update_venv_info (
226
- cls ,
227
- settings : DottedDict ,
228
- workspace_folders : list [WorkspaceFolder ],
229
- * ,
230
- window : sublime .Window ,
231
- ) -> None :
232
- window_attr = cls .window_attrs [window ]
233
-
234
- def _update_venv_info () -> None :
235
- window_attr .venv_info = None
236
-
237
- if python_path := settings .get ("python.pythonPath" ):
238
- window_attr .venv_info = find_venv_by_python_executable (python_path )
239
- return
240
-
241
- supported_finder_names = tuple (get_finder_name_mapping ().keys ())
242
- finder_names : list [str ] = settings .get ("venvStrategies" )
243
- if invalid_finder_names := sorted (set (finder_names ) - set (supported_finder_names )):
244
- log_warning (f"The following finder names are not supported: { ', ' .join (invalid_finder_names )} " )
245
-
246
- if workspace_folders and (first_folder := Path (workspace_folders [0 ].path ).resolve ()):
247
- for folder in (first_folder , * first_folder .parents ):
248
- if venv_info := find_venv_by_finder_names (finder_names , project_dir = folder ):
249
- window_attr .venv_info = venv_info
250
- return
251
-
252
- def _update_simple_python_path () -> None :
253
- window_attr .simple_python_executable = None
254
-
255
- if python_path := first_true (("py" , "python3" , "python" ), pred = shutil .which ):
256
- window_attr .simple_python_executable = Path (python_path )
257
-
258
- _update_simple_python_path ()
259
- _update_venv_info ()
0 commit comments