11# Copyright 2017 Palantir Technologies, Inc.
22import logging
3- import time
3+ import os .path as osp
4+ import parso
45from pyls import hookimpl , lsp , _utils
5- from contextlib import contextmanager
6- import signal
76
87log = logging .getLogger (__name__ )
98
2423 'builtinfunction' : lsp .CompletionItemKind .Function ,
2524 'module' : lsp .CompletionItemKind .Module ,
2625 'file' : lsp .CompletionItemKind .File ,
26+ 'path' : lsp .CompletionItemKind .Text ,
2727 'xrange' : lsp .CompletionItemKind .Class ,
2828 'slice' : lsp .CompletionItemKind .Class ,
2929 'traceback' : lsp .CompletionItemKind .Class ,
4343
4444COMPLETION_CACHE = {}
4545
46- @contextmanager
47- def timeout (time ):
48- # Register a function to raise a TimeoutError on the signal.
49- signal .signal (signal .SIGALRM , raise_timeout )
50- # Schedule the signal to be sent after ``time``.
51- signal .setitimer (signal .SIGALRM , time )
52-
53- try :
54- yield
55- except TimeoutError :
56- pass
57- finally :
58- # Unregister the signal so it won't be triggered
59- # if the timeout is not reached.
60- signal .signal (signal .SIGALRM , signal .SIG_IGN )
46+ # Types of parso nodes for which snippet is not included in the completion
47+ _IMPORTS = ('import_name' , 'import_from' )
6148
62-
63- def raise_timeout (signum , frame ):
64- raise TimeoutError
49+ # Types of parso node for errors
50+ _ERRORS = ('error_node' , )
6551
6652@hookimpl
6753def pyls_completions (config , document , position ):
68- definitions = document .jedi_script (position ).completions ()
54+ try :
55+ definitions = document .jedi_script (position ).completions ()
56+ except AttributeError as e :
57+ if 'CompiledObject' in str (e ):
58+ # Needed to handle missing CompiledObject attribute
59+ # 'sub_modules_dict'
60+ definitions = None
61+ else :
62+ raise e
63+
6964 if not definitions :
7065 return None
7166
@@ -77,9 +72,57 @@ def pyls_completions(config, document, position):
7772
7873 settings = config .plugin_settings ('jedi_completion' , document_path = document .path )
7974 should_include_params = settings .get ('include_params' )
75+ include_params = snippet_support and should_include_params and use_snippets (document , position )
76+ return [_format_completion (d , include_params ) for d in definitions ] or None
77+
78+ def is_exception_class (name ):
79+ """
80+ Determine if a class name is an instance of an Exception.
81+
82+ This returns `False` if the name given corresponds with a instance of
83+ the 'Exception' class, `True` otherwise
84+ """
85+ try :
86+ return name in [cls .__name__ for cls in Exception .__subclasses__ ()]
87+ except AttributeError :
88+ # Needed in case a class don't uses new-style
89+ # class definition in Python 2
90+ return False
91+
8092
81- result = [_format_completion (d , i , snippet_support and should_include_params ) for i , d in enumerate (definitions )] or None
82- return result
93+ def use_snippets (document , position ):
94+ """
95+ Determine if it's necessary to return snippets in code completions.
96+
97+ This returns `False` if a completion is being requested on an import
98+ statement, `True` otherwise.
99+ """
100+ line = position ['line' ]
101+ lines = document .source .split ('\n ' , line )
102+ act_lines = [lines [line ][:position ['character' ]]]
103+ line -= 1
104+ last_character = ''
105+ while line > - 1 :
106+ act_line = lines [line ]
107+ if (act_line .rstrip ().endswith ('\\ ' ) or
108+ act_line .rstrip ().endswith ('(' ) or
109+ act_line .rstrip ().endswith (',' )):
110+ act_lines .insert (0 , act_line )
111+ line -= 1
112+ if act_line .rstrip ().endswith ('(' ):
113+ # Needs to be added to the end of the code before parsing
114+ # to make it valid, otherwise the node type could end
115+ # being an 'error_node' for multi-line imports that use '('
116+ last_character = ')'
117+ else :
118+ break
119+ if '(' in act_lines [- 1 ].strip ():
120+ last_character = ')'
121+ code = '\n ' .join (act_lines ).split (';' )[- 1 ].strip () + last_character
122+ tokens = parso .parse (code )
123+ expr_type = tokens .children [0 ].type
124+ return (expr_type not in _IMPORTS and
125+ not (expr_type in _ERRORS and 'import' in code ))
83126
84127@hookimpl
85128def pyls_completion_detail (config , item ):
@@ -108,18 +151,33 @@ def _format_completion(d, i, include_params=True):
108151 'sortText' : '' , #_sort_text(d),
109152 'insertText' : d .name
110153 }
111- # if include_params and hasattr(d, 'params') and d.params:
112- # positional_args = [param for param in d.params if '=' not in param.description]
113-
114- # # For completions with params, we can generate a snippet instead
115- # completion['insertTextFormat'] = lsp.InsertTextFormat.Snippet
116- # snippet = d.name + '('
117- # for i, param in enumerate(positional_args):
118- # snippet += '${%s:%s}' % (i + 1, param.name)
119- # if i < len(positional_args) - 1:
120- # snippet += ', '
121- # snippet += ')$0'
122- # completion['insertText'] = snippet
154+
155+ if d .type == 'path' :
156+ path = osp .normpath (d .name )
157+ path = path .replace ('\\ ' , '\\ \\ ' )
158+ path = path .replace ('/' , '\\ /' )
159+ completion ['insertText' ] = path
160+
161+ if (include_params and hasattr (d , 'params' ) and d .params and
162+ not is_exception_class (d .name )):
163+ positional_args = [param for param in d .params
164+ if '=' not in param .description and
165+ param .name not in {'/' , '*' }]
166+
167+ if len (positional_args ) > 1 :
168+ # For completions with params, we can generate a snippet instead
169+ completion ['insertTextFormat' ] = lsp .InsertTextFormat .Snippet
170+ snippet = d .name + '('
171+ for i , param in enumerate (positional_args ):
172+ snippet += '${%s:%s}' % (i + 1 , param .name )
173+ if i < len (positional_args ) - 1 :
174+ snippet += ', '
175+ snippet += ')$0'
176+ completion ['insertText' ] = snippet
177+ elif len (positional_args ) == 1 :
178+ completion ['insertText' ] = d .name + '($0)'
179+ else :
180+ completion ['insertText' ] = d .name + '()'
123181
124182 return completion
125183
0 commit comments