Skip to content

Commit 811dff1

Browse files
committed
Merge remote-tracking branch 'noelladsa/refactoring-named-tuple'
2 parents d172921 + 97593fa commit 811dff1

File tree

11 files changed

+125
-113
lines changed

11 files changed

+125
-113
lines changed

bpython/autocomplete.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from bpython import inspection
4040
from bpython import importcompletion
4141
from bpython import line as lineparts
42+
from bpython.line import LinePart
4243
from bpython._py3compat import py3, try_decode
4344
from bpython.lazyre import LazyReCompile
4445

@@ -126,9 +127,10 @@ def format(self, word):
126127

127128
def substitute(self, cursor_offset, line, match):
128129
"""Returns a cursor offset and line with match swapped in"""
129-
start, end, word = self.locate(cursor_offset, line)
130-
result = start + len(match), line[:start] + match + line[end:]
131-
return result
130+
lpart = self.locate(cursor_offset, line)
131+
offset = lpart.start + len(match)
132+
changed_line = line[:lpart.start] + match + line[lpart.end:]
133+
return offset, changed_line
132134

133135
@property
134136
def shown_before_tab(self):
@@ -200,14 +202,13 @@ def matches(self, cursor_offset, line, **kwargs):
200202
cs = lineparts.current_string(cursor_offset, line)
201203
if cs is None:
202204
return None
203-
start, end, text = cs
204205
matches = set()
205-
username = text.split(os.path.sep, 1)[0]
206+
username = cs.word.split(os.path.sep, 1)[0]
206207
user_dir = os.path.expanduser(username)
207-
for filename in self.safe_glob(os.path.expanduser(text)):
208+
for filename in self.safe_glob(os.path.expanduser(cs.word)):
208209
if os.path.isdir(filename):
209210
filename += os.path.sep
210-
if text.startswith('~'):
211+
if cs.word.startswith('~'):
211212
filename = username + filename[len(user_dir):]
212213
matches.add(filename)
213214
return matches
@@ -235,25 +236,24 @@ def matches(self, cursor_offset, line, **kwargs):
235236
r = self.locate(cursor_offset, line)
236237
if r is None:
237238
return None
238-
text = r[2]
239239

240240
if locals_ is None:
241241
locals_ = __main__.__dict__
242242

243-
assert '.' in text
243+
assert '.' in r.word
244244

245-
for i in range(1, len(text) + 1):
246-
if text[-i] == '[':
245+
for i in range(1, len(r.word) + 1):
246+
if r.word[-i] == '[':
247247
i -= 1
248248
break
249-
methodtext = text[-i:]
250-
matches = set(''.join([text[:-i], m])
249+
methodtext = r.word[-i:]
250+
matches = set(''.join([r.word[:-i], m])
251251
for m in self.attr_matches(methodtext, locals_))
252252

253253
# TODO add open paren for methods via _callable_prefix (or decide not
254254
# to) unless the first character is a _ filter out all attributes
255255
# starting with a _
256-
if not text.split('.')[-1].startswith('_'):
256+
if not r.word.split('.')[-1].startswith('_'):
257257
matches = set(match for match in matches
258258
if not match.split('.')[-1].startswith('_'))
259259
return matches
@@ -340,15 +340,14 @@ def matches(self, cursor_offset, line, **kwargs):
340340
r = self.locate(cursor_offset, line)
341341
if r is None:
342342
return None
343-
start, end, orig = r
344343
_, _, dexpr = lineparts.current_dict(cursor_offset, line)
345344
try:
346345
obj = safe_eval(dexpr, locals_)
347346
except EvaluationError:
348347
return set()
349348
if isinstance(obj, dict) and obj.keys():
350349
return set("{0!r}]".format(k) for k in obj.keys()
351-
if repr(k).startswith(orig))
350+
if repr(k).startswith(r.word))
352351
else:
353352
return set()
354353

@@ -371,8 +370,7 @@ def matches(self, cursor_offset, line, **kwargs):
371370
return None
372371
if 'class' not in current_block:
373372
return None
374-
start, end, word = r
375-
return set(name for name in MAGIC_METHODS if name.startswith(word))
373+
return set(name for name in MAGIC_METHODS if name.startswith(r.word))
376374

377375
def locate(self, current_offset, line):
378376
return lineparts.current_method_definition_name(current_offset, line)
@@ -392,20 +390,20 @@ def matches(self, cursor_offset, line, **kwargs):
392390
r = self.locate(cursor_offset, line)
393391
if r is None:
394392
return None
395-
start, end, text = r
396393

397394
matches = set()
398-
n = len(text)
395+
n = len(r.word)
399396
for word in KEYWORDS:
400-
if self.method_match(word, n, text):
397+
if self.method_match(word, n, r.word):
401398
matches.add(word)
402399
for nspace in (builtins.__dict__, locals_):
403400
for word, val in iteritems(nspace):
404401
word = try_decode(word, 'ascii')
405402
# if identifier isn't ascii, don't complete (syntax error)
406403
if word is None:
407404
continue
408-
if self.method_match(word, n, text) and word != "__builtins__":
405+
if (self.method_match(word, n, r.word) and
406+
word != "__builtins__"):
409407
matches.add(_callable_postfix(val, word))
410408
return matches
411409

@@ -425,14 +423,13 @@ def matches(self, cursor_offset, line, **kwargs):
425423
r = self.locate(cursor_offset, line)
426424
if r is None:
427425
return None
428-
start, end, word = r
429426
if argspec:
430427
matches = set(name + '=' for name in argspec[1][0]
431428
if isinstance(name, string_types) and
432-
name.startswith(word))
429+
name.startswith(r.word))
433430
if py3:
434431
matches.update(name + '=' for name in argspec[1][4]
435-
if name.startswith(word))
432+
if name.startswith(r.word))
436433
return matches
437434

438435
def locate(self, current_offset, line):
@@ -446,14 +443,13 @@ def matches(self, cursor_offset, line, **kwargs):
446443
if r is None:
447444
return None
448445

449-
start, end, word = r
450446
attrs = dir('')
451447
if not py3:
452448
# decode attributes
453449
attrs = (att.decode('ascii') for att in attrs)
454450

455-
matches = set(att for att in attrs if att.startswith(word))
456-
if not word.startswith('_'):
451+
matches = set(att for att in attrs if att.startswith(r.word))
452+
if not r.word.startswith('_'):
457453
return set(match for match in matches if not match.startswith('_'))
458454
return matches
459455

@@ -513,7 +509,7 @@ def matches(self, cursor_offset, line, **kwargs):
513509
def locate(self, cursor_offset, line):
514510
start = self._orig_start
515511
end = cursor_offset
516-
return start, end, line[start:end]
512+
return LinePart(start, end, line[start:end])
517513

518514
class MultilineJediCompletion(JediCompletion):
519515
def matches(self, cursor_offset, line, **kwargs):

bpython/cli.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,9 @@ def complete(self, tab=False):
461461
list_win_visible = repl.Repl.complete(self, tab)
462462
if list_win_visible:
463463
try:
464-
self.show_list(self.matches_iter.matches, topline=self.argspec, formatter=self.matches_iter.completer.format)
464+
self.show_list(self.matches_iter.matches, self.arg_pos,
465+
topline=self.funcprops,
466+
formatter=self.matches_iter.completer.format)
465467
except curses.error:
466468
# XXX: This is a massive hack, it will go away when I get
467469
# cusswords into a good enough state that we can start
@@ -691,24 +693,23 @@ def lf(self):
691693
self.print_line(self.s, newline=True)
692694
self.echo("\n")
693695

694-
def mkargspec(self, topline, down):
696+
def mkargspec(self, topline, in_arg, down):
695697
"""This figures out what to do with the argspec and puts it nicely into
696698
the list window. It returns the number of lines used to display the
697699
argspec. It's also kind of messy due to it having to call so many
698700
addstr() to get the colouring right, but it seems to be pretty
699701
sturdy."""
700702

701703
r = 3
702-
fn = topline[0]
703-
args = topline[1][0]
704-
kwargs = topline[1][3]
705-
_args = topline[1][1]
706-
_kwargs = topline[1][2]
707-
is_bound_method = topline[2]
708-
in_arg = topline[3]
704+
fn = topline.func
705+
args = topline.argspec.args
706+
kwargs = topline.argspec.defaults
707+
_args = topline.argspec.varargs
708+
_kwargs = topline.argspec.varkwargs
709+
is_bound_method = topline.is_bound_method
709710
if py3:
710-
kwonly = topline[1][4]
711-
kwonly_defaults = topline[1][5] or dict()
711+
kwonly = topline.argspec.kwonly
712+
kwonly_defaults = topline.kwonly_defaults or dict()
712713
max_w = int(self.scr.getmaxyx()[1] * 0.6)
713714
self.list_win.erase()
714715
self.list_win.resize(3, max_w)
@@ -1253,7 +1254,7 @@ def write(self, s):
12531254
self.s_hist.append(s.rstrip())
12541255

12551256

1256-
def show_list(self, items, topline=None, formatter=None, current_item=None):
1257+
def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None):
12571258

12581259
shared = Struct()
12591260
shared.cols = 0
@@ -1275,7 +1276,7 @@ def show_list(self, items, topline=None, formatter=None, current_item=None):
12751276
current_item = formatter(current_item)
12761277

12771278
if topline:
1278-
height_offset = self.mkargspec(topline, down) + 1
1279+
height_offset = self.mkargspec(topline, arg_pos, down) + 1
12791280
else:
12801281
height_offset = 0
12811282

@@ -1454,7 +1455,8 @@ def tab(self, back=False):
14541455
current_match = back and self.matches_iter.previous() \
14551456
or next(self.matches_iter)
14561457
try:
1457-
self.show_list(self.matches_iter.matches, topline=self.argspec,
1458+
self.show_list(self.matches_iter.matches, self.arg_pos,
1459+
topline=self.funcprops,
14581460
formatter=self.matches_iter.completer.format,
14591461
current_item=current_match)
14601462
except curses.error:

bpython/curtsiesfrontend/repl.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ def add_to_incremental_search(self, char=None, backspace=False):
909909

910910
def update_completion(self, tab=False):
911911
"""Update visible docstring and matches, and possibly hide/show completion box"""
912-
# Update autocomplete info; self.matches_iter and self.argspec
912+
# Update autocomplete info; self.matches_iter and self.funcprops
913913
# Should be called whenever the completion box might need to appear / dissapear
914914
# when current line or cursor offset changes, unless via selecting a match
915915
self.current_match = None
@@ -1272,7 +1272,8 @@ def move_screen_up(current_line_start_row):
12721272
infobox = paint.paint_infobox(info_max_rows,
12731273
int(width * self.config.cli_suggestion_width),
12741274
self.matches_iter.matches,
1275-
self.argspec,
1275+
self.funcprops,
1276+
self.arg_pos,
12761277
self.current_match,
12771278
self.docstring,
12781279
self.config,

bpython/curtsiesfrontend/replpainter.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,17 @@ def matches_lines(rows, columns, matches, current, config, format):
7272
return matches_lines
7373

7474

75-
def formatted_argspec(argspec, columns, config):
75+
def formatted_argspec(funcprops, arg_pos, columns, config):
7676
# Pretty directly taken from bpython.cli
77-
is_bound_method = argspec[2]
78-
func = argspec[0]
79-
args = argspec[1][0]
80-
kwargs = argspec[1][3]
81-
_args = argspec[1][1] # *args
82-
_kwargs = argspec[1][2] # **kwargs
83-
is_bound_method = argspec[2]
84-
in_arg = argspec[3]
77+
func = funcprops.func
78+
args = funcprops.argspec.args
79+
kwargs = funcprops.argspec.defaults
80+
_args = funcprops.argspec.varargs
81+
_kwargs = funcprops.argspec.varkwargs
82+
is_bound_method = funcprops.is_bound_method
8583
if py3:
86-
kwonly = argspec[1][4]
87-
kwonly_defaults = argspec[1][5] or dict()
84+
kwonly = funcprops.argspec.kwonly
85+
kwonly_defaults = funcprops.argspec.kwonly_defaults or dict()
8886

8987
arg_color = func_for_letter(config.color_scheme['name'])
9088
func_color = func_for_letter(config.color_scheme['name'].swapcase())
@@ -95,16 +93,16 @@ def formatted_argspec(argspec, columns, config):
9593

9694
s = func_color(func) + arg_color(': (')
9795

98-
if is_bound_method and isinstance(in_arg, int):
96+
if is_bound_method and isinstance(arg_pos, int):
9997
# TODO what values could this have?
100-
in_arg += 1
98+
arg_pos += 1
10199

102100
for i, arg in enumerate(args):
103101
kw = None
104102
if kwargs and i >= len(args) - len(kwargs):
105103
kw = str(kwargs[i - (len(args) - len(kwargs))])
106-
color = token_color if in_arg in (i, arg) else arg_color
107-
if i == in_arg or arg == in_arg:
104+
color = token_color if arg_pos in (i, arg) else arg_color
105+
if i == arg_pos or arg == arg_pos:
108106
color = bolds[color]
109107

110108
if not py3:
@@ -135,7 +133,7 @@ def formatted_argspec(argspec, columns, config):
135133
for arg in kwonly:
136134
s += punctuation_color(', ')
137135
color = token_color
138-
if in_arg:
136+
if arg_pos:
139137
color = bolds[color]
140138
s += color(arg)
141139
default = kwonly_defaults.get(arg, marker)
@@ -159,13 +157,14 @@ def formatted_docstring(docstring, columns, config):
159157
for line in docstring.split('\n')), [])
160158

161159

162-
def paint_infobox(rows, columns, matches, argspec, match, docstring, config,
163-
format):
164-
"""Returns painted completions, argspec, match, docstring etc."""
160+
def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring,
161+
config, format):
162+
"""Returns painted completions, funcprops, match, docstring etc."""
165163
if not (rows and columns):
166164
return fsarray(0, 0)
167165
width = columns - 4
168-
lines = ((formatted_argspec(argspec, width, config) if argspec else []) +
166+
lines = ((formatted_argspec(funcprops, arg_pos, width, config)
167+
if funcprops else []) +
169168
(matches_lines(rows, width, matches, match, config, format)
170169
if matches else []) +
171170
(formatted_docstring(docstring, width, config)

bpython/inspection.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io
2929
import keyword
3030
import pydoc
31+
from collections import namedtuple
3132
from six.moves import range
3233

3334
from pygments.token import Token
@@ -40,6 +41,11 @@
4041

4142
_name = LazyReCompile(r'[a-zA-Z_]\w*$')
4243

44+
ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults',
45+
'kwonly', 'kwonly_defaults', 'annotations'])
46+
47+
FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method'])
48+
4349

4450
class AttrCleaner(object):
4551
"""A context manager that tries to make an object not exhibit side-effects
@@ -208,11 +214,11 @@ def getpydocspec(f, func):
208214
if default:
209215
defaults.append(default)
210216

211-
return [func, (args, varargs, varkwargs, defaults,
212-
kwonly_args, kwonly_defaults)]
217+
return ArgSpec(args, varargs, varkwargs, default, kwonly_args,
218+
kwonly_defaults, None)
213219

214220

215-
def getargspec(func, f):
221+
def getfuncprops(func, f):
216222
# Check if it's a real bound method or if it's implicitly calling __init__
217223
# (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would
218224
# not take 'self', the latter would:
@@ -238,16 +244,19 @@ def getargspec(func, f):
238244

239245
argspec = list(argspec)
240246
fixlongargs(f, argspec)
241-
argspec = [func, argspec, is_bound_method]
247+
if len(argspec) == 4:
248+
argspec = argspec + [list(), dict(), None]
249+
argspec = ArgSpec(*argspec)
250+
fprops = FuncProps(func, argspec, is_bound_method)
242251
except (TypeError, KeyError):
243252
with AttrCleaner(f):
244253
argspec = getpydocspec(f, func)
245254
if argspec is None:
246255
return None
247256
if inspect.ismethoddescriptor(f):
248-
argspec[1][0].insert(0, 'obj')
249-
argspec.append(is_bound_method)
250-
return argspec
257+
argspec.args.insert(0, 'obj')
258+
fprops = FuncProps(func, argspec, is_bound_method)
259+
return fprops
251260

252261

253262
def is_eval_safe_name(string):

0 commit comments

Comments
 (0)