forked from prompt-toolkit/ptpython
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompleter.py
More file actions
161 lines (138 loc) · 6.6 KB
/
completer.py
File metadata and controls
161 lines (138 loc) · 6.6 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from __future__ import unicode_literals
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.contrib.completers import PathCompleter
from prompt_toolkit.contrib.regular_languages.compiler import compile as compile_grammar
from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter
from ptpython.utils import get_jedi_script_from_document
import re
__all__ = (
'PythonCompleter',
)
class PythonCompleter(Completer):
"""
Completer for Python code.
"""
def __init__(self, get_globals, get_locals):
super(PythonCompleter, self).__init__()
self.get_globals = get_globals
self.get_locals = get_locals
self._path_completer_cache = None
self._path_completer_grammar_cache = None
@property
def _path_completer(self):
if self._path_completer_cache is None:
self._path_completer_cache = GrammarCompleter(
self._path_completer_grammar, {
'var1': PathCompleter(expanduser=True),
'var2': PathCompleter(expanduser=True),
})
return self._path_completer_cache
@property
def _path_completer_grammar(self):
"""
Return the grammar for matching paths inside strings inside Python
code.
"""
# We make this lazy, because it delays startup time a little bit.
# This way, the grammar is build during the first completion.
if self._path_completer_grammar_cache is None:
self._path_completer_grammar_cache = self._create_path_completer_grammar()
return self._path_completer_grammar_cache
def _create_path_completer_grammar(self):
def unwrapper(text):
return re.sub(r'\\(.)', r'\1', text)
def single_quoted_wrapper(text):
return text.replace('\\', '\\\\').replace("'", "\\'")
def double_quoted_wrapper(text):
return text.replace('\\', '\\\\').replace('"', '\\"')
grammar = r"""
# Text before the current string.
(
[^'"#] | # Not quoted characters.
''' ([^'\\]|'(?!')|''(?!')|\\.])* ''' | # Inside single quoted triple strings
"" " ([^"\\]|"(?!")|""(?!^)|\\.])* "" " | # Inside double quoted triple strings
\#[^\n]*(\n|$) | # Comment.
"(?!"") ([^"\\]|\\.)*" | # Inside double quoted strings.
'(?!'') ([^'\\]|\\.)*' # Inside single quoted strings.
# Warning: The negative lookahead in the above two
# statements is important. If we drop that,
# then the regex will try to interpret every
# triple quoted string also as a single quoted
# string, making this exponentially expensive to
# execute!
)*
# The current string that we're completing.
(
' (?P<var1>([^\n'\\]|\\.)*) | # Inside a single quoted string.
" (?P<var2>([^\n"\\]|\\.)*) # Inside a double quoted string.
)
"""
return compile_grammar(
grammar,
escape_funcs={
'var1': single_quoted_wrapper,
'var2': double_quoted_wrapper,
},
unescape_funcs={
'var1': unwrapper,
'var2': unwrapper,
})
def _complete_path_while_typing(self, document):
char_before_cursor = document.char_before_cursor
return document.text and (
char_before_cursor.isalnum() or char_before_cursor in '/.~')
def _complete_python_while_typing(self, document):
char_before_cursor = document.char_before_cursor
return document.text and (
char_before_cursor.isalnum() or char_before_cursor in '_.')
def get_completions(self, document, complete_event):
"""
Get Python completions.
"""
# Do Path completions
if complete_event.completion_requested or self._complete_path_while_typing(document):
for c in self._path_completer.get_completions(document, complete_event):
yield c
# If we are inside a string, Don't do Jedi completion.
if self._path_completer_grammar.match(document.text_before_cursor):
return
# Do Jedi Python completions.
if complete_event.completion_requested or self._complete_python_while_typing(document):
script = get_jedi_script_from_document(document, self.get_locals(), self.get_globals())
if script:
try:
completions = script.completions()
except TypeError:
# Issue #9: bad syntax causes completions() to fail in jedi.
# https://github.com/jonathanslenders/python-prompt-toolkit/issues/9
pass
except UnicodeDecodeError:
# Issue #43: UnicodeDecodeError on OpenBSD
# https://github.com/jonathanslenders/python-prompt-toolkit/issues/43
pass
except AttributeError:
# Jedi issue #513: https://github.com/davidhalter/jedi/issues/513
pass
except ValueError:
# Jedi issue: "ValueError: invalid \x escape"
pass
except KeyError:
# Jedi issue: "KeyError: u'a_lambda'."
# https://github.com/jonathanslenders/ptpython/issues/89
pass
except IOError:
# Jedi issue: "IOError: No such file or directory."
# https://github.com/jonathanslenders/ptpython/issues/71
pass
except AssertionError:
# In jedi.parser.__init__.py: 227, in remove_last_newline,
# the assertion "newline.value.endswith('\n')" can fail.
pass
except SystemError:
# File "jedi/api/helpers.py", line 140, in get_stack_at_position
# raise SystemError("This really shouldn't happen. There's a bug in Jedi.")
pass
else:
for c in completions:
yield Completion(c.name_with_symbols, len(c.complete) - len(c.name_with_symbols),
display=c.name_with_symbols)