3
3
4
4
import logging
5
5
import os .path as osp
6
+ from collections import defaultdict
7
+ from time import time
6
8
9
+ from jedi .api .classes import Completion
7
10
import parso
8
11
9
12
from pylsp import _utils , hookimpl , lsp
55
58
@hookimpl
56
59
def pylsp_completions (config , document , position ):
57
60
"""Get formatted completions for current code position"""
61
+ # pylint: disable=too-many-locals
58
62
settings = config .plugin_settings ('jedi_completion' , document_path = document .path )
59
63
code_position = _utils .position_to_jedi_linecolumn (document , position )
60
64
@@ -70,18 +74,28 @@ def pylsp_completions(config, document, position):
70
74
should_include_params = settings .get ('include_params' )
71
75
should_include_class_objects = settings .get ('include_class_objects' , True )
72
76
77
+ max_labels_resolve = settings .get ('resolve_at_most_labels' , 25 )
78
+
73
79
include_params = snippet_support and should_include_params and use_snippets (document , position )
74
80
include_class_objects = snippet_support and should_include_class_objects and use_snippets (document , position )
75
81
76
82
ready_completions = [
77
- _format_completion (c , include_params )
78
- for c in completions
83
+ _format_completion (
84
+ c ,
85
+ include_params ,
86
+ resolve_label = (i < max_labels_resolve )
87
+ )
88
+ for i , c in enumerate (completions )
79
89
]
80
90
81
91
if include_class_objects :
82
- for c in completions :
92
+ for i , c in enumerate ( completions ) :
83
93
if c .type == 'class' :
84
- completion_dict = _format_completion (c , False )
94
+ completion_dict = _format_completion (
95
+ c ,
96
+ False ,
97
+ resolve_label = (i < max_labels_resolve )
98
+ )
85
99
completion_dict ['kind' ] = lsp .CompletionItemKind .TypeParameter
86
100
completion_dict ['label' ] += ' object'
87
101
ready_completions .append (completion_dict )
@@ -139,9 +153,9 @@ def use_snippets(document, position):
139
153
not (expr_type in _ERRORS and 'import' in code ))
140
154
141
155
142
- def _format_completion (d , include_params = True ):
156
+ def _format_completion (d , include_params = True , resolve_label = False ):
143
157
completion = {
144
- 'label' : _label (d ),
158
+ 'label' : _label (d , resolve_label ),
145
159
'kind' : _TYPE_MAP .get (d .type ),
146
160
'detail' : _detail (d ),
147
161
'documentation' : _utils .format_docstring (d .docstring ()),
@@ -180,12 +194,12 @@ def _format_completion(d, include_params=True):
180
194
return completion
181
195
182
196
183
- def _label (definition ):
184
- sig = definition . get_signatures ()
185
- if definition . type in ( 'function' , 'method' ) and sig :
186
- params = ', ' . join ( param . name for param in sig [ 0 ]. params )
187
- return '{}({})' . format ( definition . name , params )
188
-
197
+ def _label (definition , resolve = False ):
198
+ if not resolve :
199
+ return definition . name
200
+ sig = LABEL_RESOLVER . get_or_create ( definition )
201
+ if sig :
202
+ return sig
189
203
return definition .name
190
204
191
205
@@ -204,3 +218,78 @@ def _sort_text(definition):
204
218
# If its 'hidden', put it next last
205
219
prefix = 'z{}' if definition .name .startswith ('_' ) else 'a{}'
206
220
return prefix .format (definition .name )
221
+
222
+
223
+ class LabelResolver :
224
+
225
+ def __init__ (self , format_label_callback , time_to_live = 60 * 30 ):
226
+ self .format_label = format_label_callback
227
+ self ._cache = {}
228
+ self ._time_to_live = time_to_live
229
+ self ._cache_ttl = defaultdict (set )
230
+ self ._clear_every = 2
231
+ # see https://github.com/davidhalter/jedi/blob/master/jedi/inference/helpers.py#L194-L202
232
+ self ._cached_modules = {'pandas' , 'numpy' , 'tensorflow' , 'matplotlib' }
233
+
234
+ def clear_outdated (self ):
235
+ now = self .time_key ()
236
+ to_clear = [
237
+ timestamp
238
+ for timestamp in self ._cache_ttl
239
+ if timestamp < now
240
+ ]
241
+ for time_key in to_clear :
242
+ for key in self ._cache_ttl [time_key ]:
243
+ del self ._cache [key ]
244
+ del self ._cache_ttl [time_key ]
245
+
246
+ def time_key (self ):
247
+ return int (time () / self ._time_to_live )
248
+
249
+ def get_or_create (self , completion : Completion ):
250
+ if not completion .full_name :
251
+ use_cache = False
252
+ else :
253
+ module_parts = completion .full_name .split ('.' )
254
+ use_cache = module_parts and module_parts [0 ] in self ._cached_modules
255
+
256
+ if use_cache :
257
+ key = self ._create_completion_id (completion )
258
+ if key not in self ._cache :
259
+ if self .time_key () % self ._clear_every == 0 :
260
+ self .clear_outdated ()
261
+
262
+ self ._cache [key ] = self .resolve_label (completion )
263
+ self ._cache_ttl [self .time_key ()].add (key )
264
+ return self ._cache [key ]
265
+
266
+ return self .resolve_label (completion )
267
+
268
+ def _create_completion_id (self , completion : Completion ):
269
+ return (
270
+ completion .full_name , completion .module_path ,
271
+ completion .line , completion .column ,
272
+ self .time_key ()
273
+ )
274
+
275
+ def resolve_label (self , completion ):
276
+ try :
277
+ sig = completion .get_signatures ()
278
+ return self .format_label (completion , sig )
279
+ except Exception as e : # pylint: disable=broad-except
280
+ log .warning (
281
+ 'Something went wrong when resolving label for {completion}: {e}' ,
282
+ completion = completion , e = e
283
+ )
284
+ return ''
285
+
286
+
287
+ def format_label (completion , sig ):
288
+ if sig and completion .type in ('function' , 'method' ):
289
+ params = ', ' .join (param .name for param in sig [0 ].params )
290
+ label = '{}({})' .format (completion .name , params )
291
+ return label
292
+ return completion .name
293
+
294
+
295
+ LABEL_RESOLVER = LabelResolver (format_label )
0 commit comments