33
44import logging
55import os .path as osp
6+ from collections import defaultdict
7+ from time import time
68
9+ from jedi .api .classes import Completion
710import parso
811
912from pylsp import _utils , hookimpl , lsp
5558@hookimpl
5659def pylsp_completions (config , document , position ):
5760 """Get formatted completions for current code position"""
61+ # pylint: disable=too-many-locals
5862 settings = config .plugin_settings ('jedi_completion' , document_path = document .path )
5963 code_position = _utils .position_to_jedi_linecolumn (document , position )
6064
@@ -70,18 +74,28 @@ def pylsp_completions(config, document, position):
7074 should_include_params = settings .get ('include_params' )
7175 should_include_class_objects = settings .get ('include_class_objects' , True )
7276
77+ max_labels_resolve = settings .get ('resolve_at_most_labels' , 25 )
78+
7379 include_params = snippet_support and should_include_params and use_snippets (document , position )
7480 include_class_objects = snippet_support and should_include_class_objects and use_snippets (document , position )
7581
7682 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 )
7989 ]
8090
8191 if include_class_objects :
82- for c in completions :
92+ for i , c in enumerate ( completions ) :
8393 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+ )
8599 completion_dict ['kind' ] = lsp .CompletionItemKind .TypeParameter
86100 completion_dict ['label' ] += ' object'
87101 ready_completions .append (completion_dict )
@@ -139,9 +153,9 @@ def use_snippets(document, position):
139153 not (expr_type in _ERRORS and 'import' in code ))
140154
141155
142- def _format_completion (d , include_params = True ):
156+ def _format_completion (d , include_params = True , resolve_label = False ):
143157 completion = {
144- 'label' : _label (d ),
158+ 'label' : _label (d , resolve_label ),
145159 'kind' : _TYPE_MAP .get (d .type ),
146160 'detail' : _detail (d ),
147161 'documentation' : _utils .format_docstring (d .docstring ()),
@@ -180,12 +194,12 @@ def _format_completion(d, include_params=True):
180194 return completion
181195
182196
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
189203 return definition .name
190204
191205
@@ -204,3 +218,78 @@ def _sort_text(definition):
204218 # If its 'hidden', put it next last
205219 prefix = 'z{}' if definition .name .startswith ('_' ) else 'a{}'
206220 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