From 9fbb325b8cdd1fb3292cf10d60a4ac0fa71849f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 26 Apr 2015 16:48:20 +0200 Subject: [PATCH 0001/1036] Test Python 3.5 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index fa6d1945b..ab0053adb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "2.7" - "3.3" - "3.4" + - "3.5" - "pypy" - "pypy3" From e952c73eb972d48d02af7e6274a48ab27ff80b9e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:32:36 +0200 Subject: [PATCH 0002/1036] PEP8 --- bpython/test/test_filewatch.py | 1 + bpython/test/test_repl.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index e9e08449e..061e18b2b 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -9,6 +9,7 @@ from bpython.test import mock, unittest + @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 710d07c15..57d83b147 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -162,7 +162,6 @@ def test_func_name_method_issue_479(self): self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) - def test_syntax_error_parens(self): for line in ["spam(]", "spam([)", "spam())"]: self.set_input_line(line) @@ -329,7 +328,8 @@ def test_fuzzy_global_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['UnboundLocalError(', '__doc__'] if not py3 else - ['ChildProcessError(', 'UnboundLocalError(', '__doc__']) + ['ChildProcessError(', 'UnboundLocalError(', + '__doc__']) # 2. Attribute tests def test_simple_attribute_complete(self): From bd2002a619f62d643f9328456cf45ec7a1011d14 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 May 2015 18:50:55 +0200 Subject: [PATCH 0003/1036] Add 0.14.2 changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0f229b3d6..c8c4bd714 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,17 @@ Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. +0.14.2 +------ + +Fixes: + +* #498: Fixed is_callable +* #509: Fixed fcntl usage. +* #523, #524: Fix conditional dependencies for SNI support again. +* Fix binary name of bpdb. + + 0.14.1 ------ From 66d7b5234fabf224d6f748cd9e3114b996934805 Mon Sep 17 00:00:00 2001 From: noella Date: Thu, 28 May 2015 22:16:45 -0400 Subject: [PATCH 0004/1036] Refactoring with named tuples --- bpython/autocomplete.py | 51 ++++++++++++++++++----------------------- bpython/line.py | 29 ++++++++++++----------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f4a7a0d7d..a172ca4c0 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,7 +2,6 @@ # The MIT License # -# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -39,6 +38,7 @@ from bpython import inspection from bpython import importcompletion from bpython import line as lineparts +from bpython.line import LinePart from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile @@ -126,8 +126,8 @@ def format(self, word): def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - start, end, word = self.locate(cursor_offset, line) - result = start + len(match), line[:start] + match + line[end:] + lpart = self.locate(cursor_offset, line) + result = lpart.start + len(match), line[:lpart.start] + match + line[lpart.end:] return result @property @@ -200,14 +200,13 @@ def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) if cs is None: return None - start, end, text = cs matches = set() - username = text.split(os.path.sep, 1)[0] + username = cs.word.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) - for filename in self.safe_glob(os.path.expanduser(text)): + for filename in self.safe_glob(os.path.expanduser(cs.word)): if os.path.isdir(filename): filename += os.path.sep - if text.startswith('~'): + if cs.word.startswith('~'): filename = username + filename[len(user_dir):] matches.add(filename) return matches @@ -235,25 +234,24 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - text = r[2] if locals_ is None: locals_ = __main__.__dict__ - assert '.' in text + assert '.' in r.word - for i in range(1, len(text) + 1): - if text[-i] == '[': + for i in range(1, len(r.word) + 1): + if r.word[-i] == '[': i -= 1 break - methodtext = text[-i:] - matches = set(''.join([text[:-i], m]) + methodtext = r.word[-i:] + matches = set(''.join([r.word[:-i], m]) for m in self.attr_matches(methodtext, locals_)) # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes # starting with a _ - if not text.split('.')[-1].startswith('_'): + if not r.word.split('.')[-1].startswith('_'): matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) return matches @@ -340,7 +338,6 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, orig = r _, _, dexpr = lineparts.current_dict(cursor_offset, line) try: obj = safe_eval(dexpr, locals_) @@ -348,7 +345,7 @@ def matches(self, cursor_offset, line, **kwargs): return set() if isinstance(obj, dict) and obj.keys(): return set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(orig)) + if repr(k).startswith(r.word)) else: return set() @@ -371,8 +368,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if 'class' not in current_block: return None - start, end, word = r - return set(name for name in MAGIC_METHODS if name.startswith(word)) + return set(name for name in MAGIC_METHODS if name.startswith(r.word)) def locate(self, current_offset, line): return lineparts.current_method_definition_name(current_offset, line) @@ -392,12 +388,11 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, text = r matches = set() - n = len(text) + n = len(r.word) for word in KEYWORDS: - if self.method_match(word, n, text): + if self.method_match(word, n, r.word): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): @@ -405,7 +400,7 @@ def matches(self, cursor_offset, line, **kwargs): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if self.method_match(word, n, text) and word != "__builtins__": + if self.method_match(word, n, r.word) and word != "__builtins__": matches.add(_callable_postfix(val, word)) return matches @@ -425,14 +420,13 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, word = r if argspec: matches = set(name + '=' for name in argspec[1][0] if isinstance(name, string_types) and - name.startswith(word)) + name.startswith(r.word)) if py3: matches.update(name + '=' for name in argspec[1][4] - if name.startswith(word)) + if name.startswith(r.word)) return matches def locate(self, current_offset, line): @@ -446,14 +440,13 @@ def matches(self, cursor_offset, line, **kwargs): if r is None: return None - start, end, word = r attrs = dir('') if not py3: # decode attributes attrs = (att.decode('ascii') for att in attrs) - matches = set(att for att in attrs if att.startswith(word)) - if not word.startswith('_'): + matches = set(att for att in attrs if att.startswith(r.word)) + if not r.word.startswith('_'): return set(match for match in matches if not match.startswith('_')) return matches @@ -513,7 +506,7 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, cursor_offset, line): start = self._orig_start end = cursor_offset - return start, end, line[start:end] + return LinePart(start, end, line[start:end]) class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): diff --git a/bpython/line.py b/bpython/line.py index 12f753142..c1da3943b 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,10 +5,11 @@ word.""" from itertools import chain +from collections import namedtuple from bpython.lazyre import LazyReCompile - current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') +LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) def current_word(cursor_offset, line): @@ -25,7 +26,7 @@ def current_word(cursor_offset, line): word = m.group() if word is None: return None - return (start, end, word) + return LinePart(start, end, word) current_dict_key_re = LazyReCompile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') @@ -36,7 +37,7 @@ def current_dict_key(cursor_offset, line): matches = current_dict_key_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: - return (m.start(1), m.end(1), m.group(1)) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -48,7 +49,7 @@ def current_dict(cursor_offset, line): matches = current_dict_re.finditer(line) for m in matches: if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: - return (m.start(1), m.end(1), m.group(1)) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -66,7 +67,7 @@ def current_string(cursor_offset, line): for m in current_string_re.finditer(line): i = 3 if m.group(3) else 4 if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset: - return m.start(i), m.end(i), m.group(i) + return LinePart(m.start(i), m.end(i), m.group(i)) return None @@ -89,7 +90,7 @@ def current_object(cursor_offset, line): s += m.group(1) if not s: return None - return start, start+len(s), s + return LinePart(start, start+len(s), s) current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') @@ -106,7 +107,7 @@ def current_object_attribute(cursor_offset, line): for m in matches: if (m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset): - return m.start(1) + start, m.end(1) + start, m.group(1) + return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) return None @@ -128,7 +129,7 @@ def current_from_import_from(cursor_offset, line): for m in matches: if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -153,7 +154,7 @@ def current_from_import_import(cursor_offset, line): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: - return start, end, m.group(1) + return LinePart(start, end, m.group(1)) return None @@ -175,7 +176,7 @@ def current_import(cursor_offset, line): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: - return start, end, m.group(1) + return LinePart(start, end, m.group(1)) current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") @@ -186,7 +187,7 @@ def current_method_definition_name(cursor_offset, line): matches = current_method_definition_name_re.finditer(line) for m in matches: if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -198,7 +199,7 @@ def current_single_word(cursor_offset, line): matches = current_single_word_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -209,7 +210,7 @@ def current_dotted_attribute(cursor_offset, line): return None start, end, word = match if '.' in word[1:]: - return start, end, word + return LinePart(start, end, word) current_string_literal_attr_re = LazyReCompile( @@ -223,5 +224,5 @@ def current_string_literal_attr(cursor_offset, line): matches = current_string_literal_attr_re.finditer(line) for m in matches: if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: - return m.start(4), m.end(4), m.group(4) + return LinePart(m.start(4), m.end(4), m.group(4)) return None From ab3ed098216f923436f9f54abed0f0f5cd0ae889 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:03:35 -0400 Subject: [PATCH 0005/1036] argspec replaced with named tuples for better readability --- bpython/inspection.py | 48 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index e0cc140df..2e3958e87 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -28,6 +28,7 @@ import io import keyword import pydoc +from collections import namedtuple from six.moves import range from pygments.token import Token @@ -40,6 +41,11 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') +Argspec = namedtuple('Argspec', ['args', 'varargs', 'varkwargs', 'defaults', + 'kwonly', 'kwonly_defaults', 'annotations']) + +FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) + class AttrCleaner(object): """A context manager that tries to make an object not exhibit side-effects @@ -175,44 +181,39 @@ def fixlongargs(f, argspec): def getpydocspec(f, func): try: - argspec = pydoc.getdoc(f) + docstring = pydoc.getdoc(f) except NameError: return None - - s = getpydocspec_re.search(argspec) + s = getpydocspec_re.search(docstring) if s is None: return None if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - args = list() - defaults = list() - varargs = varkwargs = None - kwonly_args = list() - kwonly_defaults = dict() + argspec = Argspec(list(), None, None, list(), list(), dict(), None) + for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): - varkwargs = arg[2:] + argspec.varkwargs = arg[2:] elif arg.startswith('*'): - varargs = arg[1:] + argspec.varargs = arg[1:] else: arg, _, default = arg.partition('=') - if varargs is not None: - kwonly_args.append(arg) + if argspec.varargs is not None: + argspec.kwonly_args.append(arg) if default: - kwonly_defaults[arg] = default + argspec.kwonly_defaults[arg] = default else: - args.append(arg) + argspec.args.append(arg) if default: - defaults.append(default) + argspec.defaults.append(default) - return [func, (args, varargs, varkwargs, defaults, - kwonly_args, kwonly_defaults)] + return argspec -def getargspec(func, f): +def getfuncprops(func, f): # Check if it's a real bound method or if it's implicitly calling __init__ # (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would # not take 'self', the latter would: @@ -238,16 +239,19 @@ def getargspec(func, f): argspec = list(argspec) fixlongargs(f, argspec) - argspec = [func, argspec, is_bound_method] + if len(argspec) == 4: + argspec = argspec + [list(),dict(),None] + argspec = Argspec(*argspec) + fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): with AttrCleaner(f): argspec = getpydocspec(f, func) if argspec is None: return None if inspect.ismethoddescriptor(f): - argspec[1][0].insert(0, 'obj') - argspec.append(is_bound_method) - return argspec + argspec.args.insert(0, 'obj') + fprops = FuncProps(func, argspec, is_bound_method) + return fprops def is_eval_safe_name(string): From 86a9ccefcbe3cdd0a621c504992b907fe05266e1 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:05:25 -0400 Subject: [PATCH 0006/1036] argspec replaced with namedtuple FuncProps, in_arg removed from original argspec and made into class var --- bpython/repl.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 6a9c0ebfc..40a5a4d15 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -359,7 +359,8 @@ def __init__(self, interp, config): self.history = [] self.evaluating = False self.matches_iter = MatchesIterator() - self.argspec = None + self.funcprops = None + self.arg_pos = None self.current_func = None self.highlighted_paren = None self._C = {} @@ -468,8 +469,8 @@ def get_object(self, name): def get_args(self): """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.argspec and return True, - otherwise set self.argspec to None and return False""" + argspec() for it. On success, update self.funcprops,self.arg_pos and return True, + otherwise set self.funcprops to None and return False""" self.current_func = None @@ -532,11 +533,11 @@ def get_args(self): except AttributeError: return None self.current_func = f - - self.argspec = inspection.getargspec(func, f) - if self.argspec: - self.argspec.append(arg_number) + self.funcprops = inspection.getfuncprops(func, f) + if self.funcprops: + self.arg_pos = arg_number return True + self.arg_pos = None return False def get_source_of_current_name(self): @@ -567,7 +568,7 @@ def get_source_of_current_name(self): def set_docstring(self): self.docstring = None if not self.get_args(): - self.argspec = None + self.funcprops = None elif self.current_func is not None: try: self.docstring = pydoc.getdoc(self.current_func) @@ -609,7 +610,7 @@ def complete(self, tab=False): cursor_offset=self.cursor_offset, line=self.current_line, locals_=self.interp.locals, - argspec=self.argspec, + argspec=self.funcprops, current_block='\n'.join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, history=self.history) @@ -618,7 +619,7 @@ def complete(self, tab=False): if len(matches) == 0: self.matches_iter.clear() - return bool(self.argspec) + return bool(self.funcprops) self.matches_iter.update(self.cursor_offset, self.current_line, matches, completer) From 0ace2c47e4cf6cdaf185e07cc00b190296ecf2f0 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:07:28 -0400 Subject: [PATCH 0007/1036] Replaced argspec list with namedtuple FuncProps for better readability --- bpython/cli.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 24a2e97bb..6b26d1343 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -699,16 +699,17 @@ def mkargspec(self, topline, down): sturdy.""" r = 3 - fn = topline[0] - args = topline[1][0] - kwargs = topline[1][3] - _args = topline[1][1] - _kwargs = topline[1][2] - is_bound_method = topline[2] - in_arg = topline[3] + fn = topline.func + args = topline.arginfo.args + kwargs = topline.arginfo.defaults + _args = topline.arginfo.varargs + _kwargs = topline.arginfo.varkwargs + is_bound_method = topline.is_bound_method + in_arg = topline.in_arg + print "\n\nprinting topline",topline if py3: - kwonly = topline[1][4] - kwonly_defaults = topline[1][5] or dict() + kwonly = topline.arginfo.kwonly + kwonly_defaults = topline.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) @@ -1454,7 +1455,7 @@ def tab(self, back=False): current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: - self.show_list(self.matches_iter.matches, topline=self.argspec, + self.show_list(self.matches_iter.matches, topline=self.funcprops, formatter=self.matches_iter.completer.format, current_item=current_match) except curses.error: From bd7e8e4848eff46a10054daefecc7a401a0ce5c4 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:11:14 -0400 Subject: [PATCH 0008/1036] Replacing argspec with namedtuples FuncProps, changed func signature to include arg_pos --- bpython/curtsiesfrontend/replpainter.py | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 2dcbeaef0..3e10816d2 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -72,19 +72,17 @@ def matches_lines(rows, columns, matches, current, config, format): return matches_lines -def formatted_argspec(argspec, columns, config): +def formatted_argspec(funcprops, arg_pos, columns, config): # Pretty directly taken from bpython.cli - is_bound_method = argspec[2] - func = argspec[0] - args = argspec[1][0] - kwargs = argspec[1][3] - _args = argspec[1][1] # *args - _kwargs = argspec[1][2] # **kwargs - is_bound_method = argspec[2] - in_arg = argspec[3] + func = funcprops.func + args = funcprops.argspec.args + kwargs = funcprops.argspec.defaults + _args = funcprops.argspec.varargs + _kwargs = funcprops.argspec.varkwargs + is_bound_method = funcprops.is_bound_method if py3: - kwonly = argspec[1][4] - kwonly_defaults = argspec[1][5] or dict() + kwonly = funcprops.argspec.kwonly + kwonly_defaults = funcprops.argspec.kwonly_defaults or dict() arg_color = func_for_letter(config.color_scheme['name']) func_color = func_for_letter(config.color_scheme['name'].swapcase()) @@ -95,16 +93,16 @@ def formatted_argspec(argspec, columns, config): s = func_color(func) + arg_color(': (') - if is_bound_method and isinstance(in_arg, int): + if is_bound_method and isinstance(arg_pos, int): # TODO what values could this have? - in_arg += 1 + arg_pos += 1 for i, arg in enumerate(args): kw = None if kwargs and i >= len(args) - len(kwargs): kw = str(kwargs[i - (len(args) - len(kwargs))]) - color = token_color if in_arg in (i, arg) else arg_color - if i == in_arg or arg == in_arg: + color = token_color if arg_pos in (i, arg) else arg_color + if i == arg_pos or arg == arg_pos: color = bolds[color] if not py3: @@ -135,7 +133,7 @@ def formatted_argspec(argspec, columns, config): for arg in kwonly: s += punctuation_color(', ') color = token_color - if in_arg: + if arg_pos: color = bolds[color] s += color(arg) default = kwonly_defaults.get(arg, marker) @@ -159,13 +157,13 @@ def formatted_docstring(docstring, columns, config): for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, argspec, match, docstring, config, +def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, format): - """Returns painted completions, argspec, match, docstring etc.""" + """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(argspec, width, config) if argspec else []) + + lines = ((formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) + (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) From d8fdc2fad4209e0220903860f232cd4898e1911e Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:16:03 -0400 Subject: [PATCH 0009/1036] Replaced argspec with namedtuple FuncProps --- bpython/curtsiesfrontend/repl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b460b70f5..d66d3fe06 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -909,7 +909,7 @@ def add_to_incremental_search(self, char=None, backspace=False): def update_completion(self, tab=False): """Update visible docstring and matches, and possibly hide/show completion box""" - # Update autocomplete info; self.matches_iter and self.argspec + # Update autocomplete info; self.matches_iter and self.funcprops # Should be called whenever the completion box might need to appear / dissapear # when current line or cursor offset changes, unless via selecting a match self.current_match = None @@ -1272,7 +1272,8 @@ def move_screen_up(current_line_start_row): infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), self.matches_iter.matches, - self.argspec, + self.funcprops, + self.arg_pos, self.current_match, self.docstring, self.config, From 4d6861cd005d85ad6725dccd0acf8ad82c0cbb9c Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:23:09 -0400 Subject: [PATCH 0010/1036] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_curtsies_painting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 157972108..005431128 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -109,8 +109,8 @@ def test_argspec(self): def foo(x, y, z=10): "docstring!" pass - argspec = inspection.getargspec('foo', foo) + [1] - array = replpainter.formatted_argspec(argspec, 30, setup_config()) + argspec = inspection.getfuncprops('foo', foo) + array = replpainter.formatted_argspec(argspec, 1, 30, setup_config()) screen = [bold(cyan('foo')) + cyan(':') + cyan(' ') + cyan('(') + cyan('x') + yellow(',') + yellow(' ') + bold(cyan('y')) + yellow(',') + yellow(' ') + cyan('z') + yellow('=') + From 3057bbed1c95aefad2d9cc683680f9744cad14ad Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:24:13 -0400 Subject: [PATCH 0011/1036] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_inspection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index b91f0ffbb..cf5a1aad5 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -62,22 +62,22 @@ def fails(spam=['-a', '-b']): self.assertEqual(str(['-a', '-b']), default_arg_repr, 'This test is broken (repr does not match), fix me.') - argspec = inspection.getargspec('fails', fails) - defaults = argspec[1][3] + argspec = inspection.getfuncprops('fails', fails) + defaults = argspec.argspec.defaults self.assertEqual(str(defaults[0]), default_arg_repr) def test_pasekeywordpairs_string(self): def spam(eggs="foo, bar"): pass - defaults = inspection.getargspec("spam", spam)[1][3] + defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "'foo, bar'") def test_parsekeywordpairs_multiple_keywords(self): def spam(eggs=23, foobar="yay"): pass - defaults = inspection.getargspec("spam", spam)[1][3] + defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") From 83abeb60f0406a8b0674fa1f12f155e07b502fe5 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:24:43 -0400 Subject: [PATCH 0012/1036] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_repl.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 57d83b147..8f49d17c0 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -171,22 +171,22 @@ def test_syntax_error_parens(self): def test_kw_arg_position(self): self.set_input_line("spam(a=0") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "a") + self.assertEqual(self.repl.arg_pos, "a") self.set_input_line("spam(1, b=1") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "b") + self.assertEqual(self.repl.arg_pos, "b") self.set_input_line("spam(1, c=2") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "c") + self.assertEqual(self.repl.arg_pos, "c") def test_lambda_position(self): self.set_input_line("spam(lambda a, b: 1, ") self.assertTrue(self.repl.get_args()) - self.assertTrue(self.repl.argspec) + self.assertTrue(self.repl.funcprops) # Argument position - self.assertEqual(self.repl.argspec[3], 1) + self.assertEqual(self.repl.arg_pos, 1) def test_issue127(self): self.set_input_line("x=range(") @@ -428,7 +428,7 @@ def test_simple_tab_complete(self): self.repl.print_line = mock.Mock() self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() - self.repl.argspec = mock.Mock() + self.repl.funcprops = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -471,7 +471,7 @@ def test_back_parameter(self): self.repl.matches_iter.previous.return_value = "previtem" self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() - self.repl.argspec = mock.Mock() + self.repl.funcprops = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") self.repl.print_line = mock.Mock() self.repl.s = "foo" From 6586815f155bf283963af9aa154de2b43991c603 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:55:31 -0400 Subject: [PATCH 0013/1036] Argspec - Named tuple FuncProps related refactoring --- bpython/cli.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6b26d1343..90462f990 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -461,7 +461,9 @@ def complete(self, tab=False): list_win_visible = repl.Repl.complete(self, tab) if list_win_visible: try: - self.show_list(self.matches_iter.matches, topline=self.argspec, formatter=self.matches_iter.completer.format) + self.show_list(self.matches_iter.matches, self.arg_pos, + topline=self.funcprops, + formatter=self.matches_iter.completer.format) except curses.error: # XXX: This is a massive hack, it will go away when I get # cusswords into a good enough state that we can start @@ -691,7 +693,7 @@ def lf(self): self.print_line(self.s, newline=True) self.echo("\n") - def mkargspec(self, topline, down): + def mkargspec(self, topline, in_arg, down): """This figures out what to do with the argspec and puts it nicely into the list window. It returns the number of lines used to display the argspec. It's also kind of messy due to it having to call so many @@ -700,15 +702,13 @@ def mkargspec(self, topline, down): r = 3 fn = topline.func - args = topline.arginfo.args - kwargs = topline.arginfo.defaults - _args = topline.arginfo.varargs - _kwargs = topline.arginfo.varkwargs + args = topline.argspec.args + kwargs = topline.argspec.defaults + _args = topline.argspec.varargs + _kwargs = topline.argspec.varkwargs is_bound_method = topline.is_bound_method - in_arg = topline.in_arg - print "\n\nprinting topline",topline if py3: - kwonly = topline.arginfo.kwonly + kwonly = topline.argspec.kwonly kwonly_defaults = topline.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() @@ -1254,7 +1254,7 @@ def write(self, s): self.s_hist.append(s.rstrip()) - def show_list(self, items, topline=None, formatter=None, current_item=None): + def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None): shared = Struct() shared.cols = 0 @@ -1276,7 +1276,7 @@ def show_list(self, items, topline=None, formatter=None, current_item=None): current_item = formatter(current_item) if topline: - height_offset = self.mkargspec(topline, down) + 1 + height_offset = self.mkargspec(topline, arg_pos, down) + 1 else: height_offset = 0 @@ -1455,7 +1455,8 @@ def tab(self, back=False): current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: - self.show_list(self.matches_iter.matches, topline=self.funcprops, + self.show_list(self.matches_iter.matches, self.arg_pos, + topline=self.funcprops, formatter=self.matches_iter.completer.format, current_item=current_match) except curses.error: From de6e387246fb22a272eb9e2711f6146e40fbd785 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:57:31 -0400 Subject: [PATCH 0014/1036] Refactored argspec with replacement named tuple FuncProps --- bpython/urwid.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index f10ceb60b..d2c392d72 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -745,13 +745,14 @@ def _populate_completion(self): widget_list.pop() # This is just me flailing around wildly. TODO: actually write. if self.complete(): - if self.argspec: + if self.funcprops: # This is mostly just stolen from the cli module. - func_name, args, is_bound, in_arg = self.argspec + func_name, args, is_bound = self.funcprops + in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] if py3: - kwonly = self.argspec[1][4] - kwonly_defaults = self.argspec[1][5] or {} + kwonly = self.funcprops.argspec.kwonly + kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} else: kwonly, kwonly_defaults = [], {} markup = [('bold name', func_name), From e72586a15df2a90ffc297b13f675c742e85d98e2 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:58:11 -0400 Subject: [PATCH 0015/1036] Refactored test file for argspec to FuncProp changes --- bpython/test/test_repl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 8f49d17c0..2bb13f4b8 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -429,6 +429,7 @@ def test_simple_tab_complete(self): self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() self.repl.funcprops = mock.Mock() + self.repl.arg_pos = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -472,6 +473,7 @@ def test_back_parameter(self): self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() self.repl.funcprops = mock.Mock() + self.repl.arg_pos = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") self.repl.print_line = mock.Mock() self.repl.s = "foo" From 27b788cb4e54fdaa703d485ab7dcda565224240a Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:27:19 -0400 Subject: [PATCH 0016/1036] Added accidentally deleted Copyright --- bpython/autocomplete.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a172ca4c0..0fd64a5e6 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,6 +2,7 @@ # The MIT License # +# Copyright (c) 2009-2011 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 85a2a5d02373e43f3ca958ba5b330d1109a0b689 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:31:09 -0400 Subject: [PATCH 0017/1036] Changed case for Argspec named tuple definition to ArgSpec --- bpython/inspection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 2e3958e87..258f18003 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -41,7 +41,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') -Argspec = namedtuple('Argspec', ['args', 'varargs', 'varkwargs', 'defaults', +ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -191,7 +191,7 @@ def getpydocspec(f, func): if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - argspec = Argspec(list(), None, None, list(), list(), dict(), None) + argspec = ArgSpec(list(), None, None, list(), list(), dict(), None) for arg in s.group(2).split(','): arg = arg.strip() @@ -241,7 +241,7 @@ def getfuncprops(func, f): fixlongargs(f, argspec) if len(argspec) == 4: argspec = argspec + [list(),dict(),None] - argspec = Argspec(*argspec) + argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): with AttrCleaner(f): From 2141cf78bcd58ef4978935494a1fc904e7a57380 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:35:45 -0400 Subject: [PATCH 0018/1036] Updated copyright year --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 0fd64a5e6..fcf011434 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,7 +2,7 @@ # The MIT License # -# Copyright (c) 2009-2011 the bpython authors. +# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 6dfbc4de87fd1f7c9d10f4457515f0b07b42d79b Mon Sep 17 00:00:00 2001 From: noella Date: Tue, 9 Jun 2015 13:39:49 -0400 Subject: [PATCH 0019/1036] Fixed a named tuple being modifying causing exceptions --- bpython/inspection.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 258f18003..7a700a700 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -181,36 +181,41 @@ def fixlongargs(f, argspec): def getpydocspec(f, func): try: - docstring = pydoc.getdoc(f) + argspec = pydoc.getdoc(f) except NameError: return None - s = getpydocspec_re.search(docstring) + + s = getpydocspec_re.search(argspec) if s is None: return None if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - argspec = ArgSpec(list(), None, None, list(), list(), dict(), None) - + args = list() + defaults = list() + varargs = varkwargs = None + kwonly_args = list() + kwonly_defaults = dict() for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): - argspec.varkwargs = arg[2:] + varkwargs = arg[2:] elif arg.startswith('*'): - argspec.varargs = arg[1:] + varargs = arg[1:] else: arg, _, default = arg.partition('=') - if argspec.varargs is not None: - argspec.kwonly_args.append(arg) + if varargs is not None: + kwonly_args.append(arg) if default: - argspec.kwonly_defaults[arg] = default + kwonly_defaults[arg] = default else: - argspec.args.append(arg) + args.append(arg) if default: - argspec.defaults.append(default) + defaults.append(default) - return argspec + return ArgSpec(args, varargs, varkwargs, default, kwonly_args, + kwonly_defaults, None) def getfuncprops(func, f): From d172921c95fd8dbed297ba2f9e3a12a4d32f7a8d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Jun 2015 22:43:01 +0200 Subject: [PATCH 0020/1036] Make pep8 happy Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/manual_readline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 11a4efa02..02c7cbf03 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -295,6 +295,7 @@ def transpose_word_before_cursor(cursor_offset, line): # bonus functions (not part of readline) + @edit_keys.on('') def uppercase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented From a7d729e3cbce651f5a8b8be38adf8ae6e9f63c2f Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:56:58 -0400 Subject: [PATCH 0021/1036] PEP8 --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 258f18003..511ef6895 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -42,7 +42,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', - 'kwonly', 'kwonly_defaults', 'annotations']) + 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -240,7 +240,7 @@ def getfuncprops(func, f): argspec = list(argspec) fixlongargs(f, argspec) if len(argspec) == 4: - argspec = argspec + [list(),dict(),None] + argspec = argspec + [list(), dict(), None] argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): From 1424a229d882620748bc1c236b1d5109cdc905ef Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:58:08 -0400 Subject: [PATCH 0022/1036] PEP8 --- bpython/autocomplete.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fcf011434..b2fbc3880 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -127,9 +127,10 @@ def format(self, word): def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - lpart = self.locate(cursor_offset, line) - result = lpart.start + len(match), line[:lpart.start] + match + line[lpart.end:] - return result + lpart = self.locate(cursor_offset, line) + offset = lpart.start + len(match) + changed_line = line[:lpart.start] + match + line[lpart.end:] + return offset, changed_line @property def shown_before_tab(self): @@ -401,7 +402,8 @@ def matches(self, cursor_offset, line, **kwargs): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if self.method_match(word, n, r.word) and word != "__builtins__": + if (self.method_match(word, n, r.word) and + word != "__builtins__"): matches.add(_callable_postfix(val, word)) return matches From b89969642ccc876c23506f807d005792c4dd80dc Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:59:37 -0400 Subject: [PATCH 0023/1036] PEP8 --- bpython/curtsiesfrontend/replpainter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 3e10816d2..94e813ecf 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -157,13 +157,14 @@ def formatted_docstring(docstring, columns, config): for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, - format): +def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, + config, format): """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) + + lines = ((formatted_argspec(funcprops, arg_pos, width, config) + if funcprops else []) + (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) From 191e43486bd6f27743c45716cb81ff11227d3e1a Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 01:00:18 -0400 Subject: [PATCH 0024/1036] PEP8 --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 40a5a4d15..433d2732a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -469,8 +469,8 @@ def get_object(self, name): def get_args(self): """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.funcprops,self.arg_pos and return True, - otherwise set self.funcprops to None and return False""" + argspec() for it. On success, update self.funcprops,self.arg_pos and + return True, otherwise set self.funcprops to None and return False""" self.current_func = None From 72be2cd09c45f19a6b866d6d838d2805f8ba9cf6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Jun 2015 14:56:43 +0200 Subject: [PATCH 0025/1036] Failing test for #544 --- bpython/test/test_autocomplete.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bfe1b0b66..8ec494a09 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -20,7 +20,8 @@ from bpython._py3compat import py3 from bpython.test import mock -if sys.version_info[:2] >= (3, 4): +is_py34 = sys.version_info[:2] >= (3, 4) +if is_py34: glob_function = 'glob.iglob' else: glob_function = 'glob.glob' @@ -308,6 +309,13 @@ def test_completions_starting_with_different_cases(self): [Comp('Abc', 'bc'), Comp('ade', 'de')]) self.assertSetEqual(matches, set(['ade'])) + @unittest.skipUnless(is_py34, 'asyncio required') + def test_issue_544(self): + com = autocomplete.MultilineJediCompletion() + code = '@asyncio.coroutine\ndef' + history = ('import asyncio', '@asyncio.coroutin') + com.matches(3, 'def', current_block=code, history=history) + class TestGlobalCompletion(unittest.TestCase): From 04ca930a703ab70fae18ba08c810a84ba1fb5cab Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Jun 2015 14:57:34 +0200 Subject: [PATCH 0026/1036] Work around jedi bugs (fixes #544) This might break jedi based completion. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b2fbc3880..754b3a394 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -479,11 +479,9 @@ def matches(self, cursor_offset, line, **kwargs): script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') completions = script.completions() - except jedi.NotFoundError: - self._orig_start = None - return None - except IndexError: - # for #483 + except (jedi.NotFoundError, IndexError, KeyError): + # IndexError for #483 + # KeyError for #544 self._orig_start = None return None From e1b0dcd94f005a700431dd217ad617d7b6208692 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Jun 2015 22:00:44 +0200 Subject: [PATCH 0027/1036] Do not print version and help with -q (#fixes #527) Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 88dc55009..693fde8a3 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -75,7 +75,8 @@ def main(args=None, locals_=None, banner=None): # expected for interactive sessions (vanilla python does it) sys.path.insert(0, '') - print(bpargs.version_banner()) + if not options.quiet: + print(bpargs.version_banner()) try: exit_value = mainloop(config, locals_, banner, interp, paste, interactive=(not exec_args)) From ff4ad26ca93cbde3472c29a73b7c2b56b56a11f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Jun 2015 22:08:29 +0200 Subject: [PATCH 0028/1036] Allow multi-line banners (fixes #538) Also distinguish between banners and welcome message. Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 693fde8a3..91843120e 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -28,7 +28,11 @@ # WARNING Will be a problem if more than one repl is ever instantiated this way -def main(args=None, locals_=None, banner=None): +def main(args=None, locals_=None, banner=None, welcome_message=None): + """ + banner is displayed directly after the version information. + welcome_message is passed on to Repl and displayed in the statusbar. + """ translations.init() config, options, exec_args = bpargs.parse(args, ( @@ -77,8 +81,10 @@ def main(args=None, locals_=None, banner=None): if not options.quiet: print(bpargs.version_banner()) + if banner is not None: + print(banner) try: - exit_value = mainloop(config, locals_, banner, interp, paste, + exit_value = mainloop(config, locals_, welcome_message, interp, paste, interactive=(not exec_args)) except (SystemExitFromCodeGreenlet, SystemExit) as e: exit_value = e.args From b71f41138732d862a72342b34407092a3e4eb162 Mon Sep 17 00:00:00 2001 From: sharow Date: Mon, 6 Jul 2015 06:37:28 +0900 Subject: [PATCH 0029/1036] fix: Reload and Auto-reloading doesn't work when using Python3.x --- bpython/curtsiesfrontend/repl.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d66d3fe06..a5f5dfa03 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -450,7 +450,7 @@ def smarter_request_reload(files_modified=()): self.incremental_search_target = '' - self.original_modules = sys.modules.keys() + self.original_modules = set(sys.modules.keys()) self.width = None self.height = None @@ -846,9 +846,8 @@ def clear_modules_and_reevaluate(self): if self.watcher: self.watcher.reset() cursor, line = self.cursor_offset, self.current_line - for modname in sys.modules.keys(): - if modname not in self.original_modules: - del sys.modules[modname] + for modname in (set(sys.modules.keys()) - self.original_modules): + del sys.modules[modname] self.reevaluate(insert_into_history=True) self.cursor_offset, self.current_line = cursor, line self.status_bar.message(_('Reloaded at %s by user.') % From 5eed6afc19c4a57026ef2d230df4573d0638bf8e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 7 Jul 2015 20:01:40 +0200 Subject: [PATCH 0030/1036] Fix cli after named-tuple refactoring Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 90462f990..b04a0636e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -709,7 +709,7 @@ def mkargspec(self, topline, in_arg, down): is_bound_method = topline.is_bound_method if py3: kwonly = topline.argspec.kwonly - kwonly_defaults = topline.kwonly_defaults or dict() + kwonly_defaults = topline.argspec.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) From 2c0958fb689662081d24bcdc2e951f51f7174f23 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 7 Jul 2015 20:03:51 +0200 Subject: [PATCH 0031/1036] Fix Python 3 compat (fixes #550) Signed-off-by: Sebastian Ramacher --- bpython/pager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/pager.py b/bpython/pager.py index 20b1af743..70448672f 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,6 +29,8 @@ import sys import shlex +from bpython._py3compat import py3 + def get_pager_command(default='less -rf'): command = shlex.split(os.environ.get('PAGER', default)) @@ -51,7 +53,7 @@ def page(data, use_internal=False): curses.endwin() try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) - if isinstance(data, unicode): + if py3 or isinstance(data, unicode): data = data.encode(sys.__stdout__.encoding, 'replace') popen.stdin.write(data) popen.stdin.close() From dc0f2945b2176d4741a8b8688105972809c4c7e7 Mon Sep 17 00:00:00 2001 From: sharow Date: Sat, 11 Jul 2015 17:35:32 +0900 Subject: [PATCH 0032/1036] add test --- bpython/test/test_curtsies_repl.py | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 5f85825e0..1e405bffc 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -6,6 +6,7 @@ import sys import tempfile import io +from functools import partial from contextlib import contextmanager from six.moves import StringIO @@ -284,6 +285,68 @@ def test_variable_is_cleared(self): self.assertNotIn('b', self.repl.interp.locals) +class TestCurtsiesReevaluateWithImport(TestCase): + def setUp(self): + self.repl = create_repl() + self.open = partial(io.open, mode='wt', encoding='utf-8') + self.dont_write_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + + def tearDown(self): + sys.dont_write_bytecode = self.dont_write_bytecode + + def push(self, line): + self.repl._current_line = line + self.repl.on_enter() + + def head(self, path): + self.push('import sys') + self.push('sys.path.append("%s")' % (path)) + + @staticmethod + @contextmanager + def tempfile(): + with tempfile.NamedTemporaryFile(suffix='.py') as temp: + path, name = os.path.split(temp.name) + yield temp.name, path, name.replace('.py', '') + + def test_module_content_changed(self): + with self.tempfile() as (fullpath, path, modname): + with self.open(fullpath) as f: + f.write('a = 0\n') + self.head(path) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) + with self.open(fullpath) as f: + f.write('a = 1\n') + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) + + def test_import_module_with_rewind(self): + with self.tempfile() as (fullpath, path, modname): + with self.open(fullpath) as f: + f.write('a = 0\n') + self.head(path) + self.push('import %s' % (modname)) + self.assertIn(modname, self.repl.interp.locals) + self.repl.undo() + self.assertNotIn(modname, self.repl.interp.locals) + self.repl.clear_modules_and_reevaluate() + self.assertNotIn(modname, self.repl.interp.locals) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) + with self.open(fullpath) as f: + f.write('a = 1\n') + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) + + class TestCurtsiesPagerText(TestCase): def setUp(self): From 51ffb81c2c7b49dfaf1e126cfb5836d4e5042b44 Mon Sep 17 00:00:00 2001 From: Pete Anderson Date: Tue, 14 Jul 2015 04:43:37 -0400 Subject: [PATCH 0033/1036] Keep autocomplete errors from crashing bpython Perhaps a popup of some sort informing the user that an error has occurred would be better than just swallowing the error as I've done here, but I feel like a misbehaving completer should crash the application. The completer that prompted this for me is FilenameCompletion. I've got a test file in my directory created with `touch $'with\xFFhigh ascii'. If I type an open quote and a w in bpython, it crashes. It's because From python, if I do: >>> import glob >>> glob.glob(u'w*') # this is what FileCompletion will end up calling [u'without high ascii', u'with\uf0ffhigh ascii'] >>> But if I do it from bpython: >>> import glob >>> glob.glob(u'w*'0 [u'without high ascii', 'with\xffhigh ascii'] >>> For some reason, glob is returning one unicode and one str. Then when get_completer calls sorted(matches), sorted throws up when it tries to decode the str from ASCII. I don't know why glob is behaving this way or what the fix is, but I do know that it's not worth crashing bpython whenever I type 'w --- bpython/autocomplete.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 754b3a394..5ee4f2fa8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -544,10 +544,14 @@ def get_completer(completers, cursor_offset, line, **kwargs): double underscore methods like __len__ in method signatures """ - for completer in completers: - matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return sorted(matches), (completer if matches else None) + try: + for completer in completers: + matches = completer.matches(cursor_offset, line, **kwargs) + if matches is not None: + return sorted(matches), (completer if matches else None) + except: + pass + return [], None From d9aec6769076d521cb186d71f513338415c8d398 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 2 Aug 2015 21:12:46 +0200 Subject: [PATCH 0034/1036] Fix attrib decorator (fixes #553) Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 6 ++++-- bpython/test/test_crashers.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 62b49aab5..ef53cfda9 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -14,8 +14,10 @@ try: from nose.plugins.attrib import attr except ImportError: - def attr(func, *args, **kwargs): - return func + def attr(*args, **kwargs): + def identity(func): + return func + return identity @attr(speed='slow') diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 5db661bbf..1aea547e7 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -27,8 +27,10 @@ class TrialTestCase(object): try: from nose.plugins.attrib import attr except ImportError: - def attr(func, *args, **kwargs): - return func + def attr(*args, **kwargs): + def identity(func): + return func + return identity TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") From a17999d57f5b8e9872dde39894c6764a839bd275 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 Aug 2015 21:03:07 +0200 Subject: [PATCH 0035/1036] Fix a typo Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 14aec69f2..0dfb4feff 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -214,7 +214,7 @@ def getpydocspec(f, func): if default: defaults.append(default) - return ArgSpec(args, varargs, varkwargs, default, kwonly_args, + return ArgSpec(args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None) From c08b42d6dcc31f136e556d9ca2c7549f638ea1bd Mon Sep 17 00:00:00 2001 From: Weston Vial Date: Wed, 26 Aug 2015 15:39:12 -0400 Subject: [PATCH 0036/1036] Fix bug #548 - Transpose when empty line crashes --- bpython/curtsiesfrontend/manual_readline.py | 6 ++++-- bpython/test/test_manual_readline.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 02c7cbf03..24a1b9a27 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -280,11 +280,13 @@ def yank_prev_killed_text(cursor_offset, line, cut_buffer): @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): + if cursor_offset == 0: + return cursor_offset, line return (min(len(line), cursor_offset + 1), - line[:cursor_offset-1] + + line[:cursor_offset - 1] + (line[cursor_offset] if len(line) > cursor_offset else '') + line[cursor_offset - 1] + - line[cursor_offset+1:]) + line[cursor_offset + 1:]) @edit_keys.on('') diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 6ef610638..f1e24b780 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -201,6 +201,16 @@ def test_transpose_character_before_cursor(self): "adf s|asdf", "adf as|sdf"], transpose_character_before_cursor) + def test_transpose_empty_line(self): + self.assertEquals(transpose_character_before_cursor(0, ''), + (0,'')) + + def test_transpose_first_character(self): + self.assertEquals(transpose_character_before_cursor(0, 'a'), + transpose_character_before_cursor(0, 'a')) + self.assertEquals(transpose_character_before_cursor(0, 'as'), + transpose_character_before_cursor(0, 'as')) + def test_transpose_word_before_cursor(self): pass From 3abb483b4a109cbfe61d8ed6ca7e5eba3f9fa38f Mon Sep 17 00:00:00 2001 From: Weston Vial Date: Wed, 26 Aug 2015 16:14:14 -0400 Subject: [PATCH 0037/1036] Transpose characters if cursor is at the end of the line. Mimics emacs behavior. --- bpython/curtsiesfrontend/manual_readline.py | 4 +++- bpython/test/test_manual_readline.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 24a1b9a27..a919df14d 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -280,8 +280,10 @@ def yank_prev_killed_text(cursor_offset, line, cut_buffer): @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): - if cursor_offset == 0: + if cursor_offset < 2: return cursor_offset, line + if cursor_offset == len(line): + return cursor_offset, line[:-2] + line[-1] + line[-2] return (min(len(line), cursor_offset + 1), line[:cursor_offset - 1] + (line[cursor_offset] if len(line) > cursor_offset else '') + diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index f1e24b780..3c25e3bb5 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -207,9 +207,15 @@ def test_transpose_empty_line(self): def test_transpose_first_character(self): self.assertEquals(transpose_character_before_cursor(0, 'a'), - transpose_character_before_cursor(0, 'a')) + (0, 'a')) self.assertEquals(transpose_character_before_cursor(0, 'as'), - transpose_character_before_cursor(0, 'as')) + (0, 'as')) + + def test_transpose_end_of_line(self): + self.assertEquals(transpose_character_before_cursor(1, 'a'), + (1, 'a')) + self.assertEquals(transpose_character_before_cursor(2, 'as'), + (2, 'sa')) def test_transpose_word_before_cursor(self): pass From 563f5cb8a9dfeada479d479433b9a5e78f8fea23 Mon Sep 17 00:00:00 2001 From: Jeppe Toustrup Date: Thu, 1 Oct 2015 22:53:38 +0200 Subject: [PATCH 0038/1036] Filter out two underscore attributes in auto completion This change will require you to write two underscores in order to get autocompletion of attributes starting with two underscores, as requested in #528. Fixes #528 --- bpython/autocomplete.py | 7 ++++++- bpython/test/test_autocomplete.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 754b3a394..9bb86d895 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -253,7 +253,12 @@ def matches(self, cursor_offset, line, **kwargs): # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes # starting with a _ - if not r.word.split('.')[-1].startswith('_'): + if r.word.split('.')[-1].startswith('__'): + pass + elif r.word.split('.')[-1].startswith('_'): + matches = set(match for match in matches + if not match.split('.')[-1].startswith('__')) + else: matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) return matches diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 8ec494a09..3681485db 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -245,12 +245,12 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(3, 'a._', locals_={'a': OldStyleFoo()})) + self.com.matches(3, 'a.__', locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', - self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) + self.com.matches(3, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): @@ -260,7 +260,7 @@ def __getattr__(self, attr): locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', - self.com.matches(3, 'a._', locals_=locals_)) + self.com.matches(3, 'a.__', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 9f3460b6c4d6619c9080faf4b3e2e6755c7f354b Mon Sep 17 00:00:00 2001 From: Jeppe Toustrup Date: Mon, 5 Oct 2015 22:12:00 +0200 Subject: [PATCH 0039/1036] Correct auto complete tests after double underscore change --- bpython/test/test_autocomplete.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3681485db..24018f3d2 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -245,12 +245,12 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(3, 'a.__', locals_={'a': OldStyleFoo()})) + self.com.matches(4, 'a.__', locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', - self.com.matches(3, 'A.__', locals_={'A': OldStyleFoo})) + self.com.matches(4, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): @@ -260,7 +260,7 @@ def __getattr__(self, attr): locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', - self.com.matches(3, 'a.__', locals_=locals_)) + self.com.matches(4, 'a.__', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 4df988b5b7f192eb4ceec8217c7290f6fefe0d90 Mon Sep 17 00:00:00 2001 From: Shibo Yao Date: Wed, 7 Oct 2015 07:31:34 -0500 Subject: [PATCH 0040/1036] Fix python 3 compatibility Python 3 doesn't allow addition of dict_items and list, but union works. --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index b04a0636e..3d68b68ca 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -281,7 +281,7 @@ def make_colors(config): } if platform.system() == 'Windows': - c = dict(c.items() + + c = dict(c.items() | [ ('K', 8), ('R', 9), From 668bf06f8b7ec8c239e43f359591257d7993e6c9 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Wed, 25 Nov 2015 23:58:21 -0500 Subject: [PATCH 0041/1036] Show __new__ docstrings. Fixes #572 --- bpython/inspection.py | 4 +++- bpython/repl.py | 20 +++++++++++++++----- bpython/test/test_repl.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0dfb4feff..c4f05c086 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -231,7 +231,9 @@ def getfuncprops(func, f): try: is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or (func_name == '__init__' and not - func.endswith('.__init__'))) + func.endswith('.__init__')) + or (func_name == '__new__' and not + func.endswith('.__new__'))) except: # if f is a method from a xmlrpclib.Server instance, func_name == # '__init__' throws xmlrpclib.Fault (see #202) diff --git a/bpython/repl.py b/bpython/repl.py index 433d2732a..dc13b2f6e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -527,11 +527,21 @@ def get_args(self): return False if inspect.isclass(f): - try: - if f.__init__ is not object.__init__: - f = f.__init__ - except AttributeError: - return None + class_f = None + + if (hasattr(f, '__init__') and + f.__init__ is not object.__init__): + class_f = f.__init__ + if ((not class_f or + not inspection.getfuncprops(func, class_f)) and + hasattr(f, '__new__') and + f.__new__ is not object.__new__ and + f.__new__.__class__ is not object.__new__.__class__): # py3 + class_f = f.__new__ + + if class_f: + f = class_f + self.current_func = f self.funcprops = inspection.getfuncprops(func, f) if self.funcprops: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2bb13f4b8..08ed7564c 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -138,6 +138,14 @@ def setUp(self): self.repl.push(" def spam(self, a, b, c):\n", False) self.repl.push(" pass\n", False) self.repl.push("\n", False) + self.repl.push("class SpammitySpam(object):\n", False) + self.repl.push(" def __init__(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) + self.repl.push("class WonderfulSpam(object):\n", False) + self.repl.push(" def __new__(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) self.repl.push("o = Spam()\n", False) self.repl.push("\n", False) @@ -207,6 +215,13 @@ def test_nonexistent_name(self): self.set_input_line("spamspamspam(") self.assertFalse(self.repl.get_args()) + def test_issue572(self): + self.set_input_line("SpammitySpam(") + self.assertTrue(self.repl.get_args()) + + self.set_input_line("WonderfulSpam(") + self.assertTrue(self.repl.get_args()) + class TestGetSource(unittest.TestCase): def setUp(self): From f16a248b5e933c22e03401d4f2ef11ab3dc0a3ec Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 02:08:28 +0000 Subject: [PATCH 0042/1036] Array item completion is working with strings. Pressing tab works for autocompletion of array items now --- bpython/autocomplete.py | 39 +++++++++++++++++++++++++++++++++++++++ bpython/line.py | 24 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 9bb86d895..1cca269f4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -335,6 +335,44 @@ def list_attributes(self, obj): return dir(obj) +class ArrayObjectMembersCompletion(BaseCompletionType): + + def __init__(self, shown_before_tab=True, mode=SIMPLE): + self._shown_before_tab = shown_before_tab + self.completer = AttrCompletion(mode=mode) + + def matches(self, cursor_offset, line, **kwargs): + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + + r = self.locate(cursor_offset, line) + if r is None: + return None + member_part = r[2] + _, _, dexpr = lineparts.current_array_with_indexer(cursor_offset, line) + try: + locals_['temp_val_from_array'] = safe_eval(dexpr, locals_) + except (EvaluationError, IndexError): + return set() + + temp_line = line.replace(member_part, 'temp_val_from_array.') + + matches = self.completer.matches(len(temp_line), temp_line, **kwargs) + matches_with_correct_name = \ + set(match.replace('temp_val_from_array.', member_part) for match in matches) + + del locals_['temp_val_from_array'] + + return matches_with_correct_name + + def locate(self, current_offset, line): + return lineparts.current_array_item_member_name(current_offset, line) + + def format(self, match): + return after_last_dot(match) + + class DictKeyCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): @@ -565,6 +603,7 @@ def get_default_completer(mode=SIMPLE): MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), GlobalCompletion(mode=mode), + ArrayObjectMembersCompletion(mode=mode), CumulativeCompleter((AttrCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode) diff --git a/bpython/line.py b/bpython/line.py index c1da3943b..52c7b2355 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -226,3 +226,27 @@ def current_string_literal_attr(cursor_offset, line): if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: return LinePart(m.start(4), m.end(4), m.group(4)) return None + + +current_array_with_indexer_re = LazyReCompile( + r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\])\.(.*)''') + + +def current_array_with_indexer(cursor_offset, line): + """an array and indexer, e.g. foo[1]""" + matches = current_array_with_indexer_re.finditer(line) + for m in matches: + if m.start(1) <= cursor_offset and m.end(1) <= cursor_offset: + return LinePart(m.start(1), m.end(1), m.group(1)) + + +current_array_item_member_name_re = LazyReCompile( + r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\]\.)(.*)''') + + +def current_array_item_member_name(cursor_offset, line): + """the member name after an array indexer, e.g. foo[1].bar""" + matches = current_array_item_member_name_re.finditer(line) + for m in matches: + if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: + return LinePart(m.start(1), m.end(2), m.group(1)) From b2867705927b7dbcd109f9382b5280e8da9a76fa Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 18:04:52 +0000 Subject: [PATCH 0043/1036] Able to handle nested arrays now when autocompleting for array items --- bpython/line.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 52c7b2355..15495d988 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -229,7 +229,7 @@ def current_string_literal_attr(cursor_offset, line): current_array_with_indexer_re = LazyReCompile( - r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\])\.(.*)''') + r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+)\.(.*)''') def current_array_with_indexer(cursor_offset, line): @@ -241,7 +241,7 @@ def current_array_with_indexer(cursor_offset, line): current_array_item_member_name_re = LazyReCompile( - r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\]\.)(.*)''') + r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+\.)(.*)''') def current_array_item_member_name(cursor_offset, line): From ce1eb1aa2eb29c75a7e6a3a21c27e04dba4cc8b9 Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 18:59:28 +0000 Subject: [PATCH 0044/1036] Add tests --- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1cca269f4..58711955a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -335,7 +335,7 @@ def list_attributes(self, obj): return dir(obj) -class ArrayObjectMembersCompletion(BaseCompletionType): +class ArrayItemMembersCompletion(BaseCompletionType): def __init__(self, shown_before_tab=True, mode=SIMPLE): self._shown_before_tab = shown_before_tab @@ -603,7 +603,7 @@ def get_default_completer(mode=SIMPLE): MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), GlobalCompletion(mode=mode), - ArrayObjectMembersCompletion(mode=mode), + ArrayItemMembersCompletion(mode=mode), CumulativeCompleter((AttrCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 24018f3d2..d0e75f878 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -263,6 +263,22 @@ def __getattr__(self, attr): self.com.matches(4, 'a.__', locals_=locals_)) +class TestArrayItemCompletion(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.com = autocomplete.ArrayItemMembersCompletion() + + def test_att_matches_found_on_instance(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [Foo()]}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + @skip_old_style + def test_att_matches_found_on_old_style_instance(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': [OldStyleFoo()]}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + class TestMagicMethodCompletion(unittest.TestCase): def test_magic_methods_complete_after_double_underscores(self): From 58f3edfb26875450bbd71b909cb003f6b942bdab Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 15:21:22 -0500 Subject: [PATCH 0045/1036] Allow global completion in brackets Make DictKeyCompletion return None when no matches found so other completers can take over. Perhaps ideal would be comulative, but this seems like good behavior -- it's clear to the user in most cases that what's now being completed are keys, then other completion. --- bpython/autocomplete.py | 9 +++++---- bpython/test/test_autocomplete.py | 15 ++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 58711955a..1370a7fa4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -387,12 +387,13 @@ def matches(self, cursor_offset, line, **kwargs): try: obj = safe_eval(dexpr, locals_) except EvaluationError: - return set() + return None if isinstance(obj, dict) and obj.keys(): - return set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(r.word)) + matches = set("{0!r}]".format(k) for k in obj.keys() + if repr(k).startswith(r.word)) + return matches if matches else None else: - return set() + return None def locate(self, current_offset, line): return lineparts.current_dict_key(current_offset, line) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index d0e75f878..a8ec672de 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -190,20 +190,25 @@ def test_set_of_keys_returned_when_matches_found(self): self.assertSetEqual(com.matches(2, "d[", locals_=local), set(["'ab']", "'cd']"])) - def test_empty_set_returned_when_eval_error(self): + def test_none_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() local = {'e': {"ab": 1, "cd": 2}} - self.assertSetEqual(com.matches(2, "d[", locals_=local), set()) + self.assertEqual(com.matches(2, "d[", locals_=local), None) - def test_empty_set_returned_when_not_dict_type(self): + def test_none_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local = {'l': ["ab", "cd"]} - self.assertSetEqual(com.matches(2, "l[", locals_=local), set()) + self.assertEqual(com.matches(2, "l[", locals_=local), None) + + def test_none_returned_when_no_matches_left(self): + com = autocomplete.DictKeyCompletion() + local = {'d': {"ab": 1, "cd": 2}} + self.assertEqual(com.matches(3, "d[r", locals_=local), None) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local = {'mNumPy': MockNumPy()} - self.assertSetEqual(com.matches(7, "mNumPy[", locals_=local), set()) + self.assertEqual(com.matches(7, "mNumPy[", locals_=local), None) class Foo(object): From 05c83beb32b1c754e127fe40f8e09c7d2d3abf4d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 18:39:12 -0500 Subject: [PATCH 0046/1036] Avoid unsafe completion on array elements This commit removes functionality it would be nice to reimplement but wasn't safe: * completion on nested arrays ( list[1][2].a ) * completion on subclasses of list, tuple etc. --- bpython/autocomplete.py | 43 +++++++++++++++++++++------- bpython/line.py | 39 ++++++++++++++++--------- bpython/test/test_autocomplete.py | 26 +++++++++++++++++ bpython/test/test_line_properties.py | 40 ++++++++++++++++++++++++-- 4 files changed, 122 insertions(+), 26 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1370a7fa4..e1c34090d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -118,8 +118,10 @@ def matches(self, cursor_offset, line, **kwargs): raise NotImplementedError def locate(self, cursor_offset, line): - """Returns a start, stop, and word given a line and cursor, or None - if no target for this type of completion is found under the cursor""" + """Returns a Linepart namedtuple instance or None given cursor and line + + A Linepart namedtuple contains a start, stop, and word. None is returned + if no target for this type of completion is found under the cursor.""" raise NotImplementedError def format(self, word): @@ -346,28 +348,47 @@ def matches(self, cursor_offset, line, **kwargs): return None locals_ = kwargs['locals_'] - r = self.locate(cursor_offset, line) - if r is None: + full = self.locate(cursor_offset, line) + if full is None: return None - member_part = r[2] - _, _, dexpr = lineparts.current_array_with_indexer(cursor_offset, line) + + arr = lineparts.current_indexed_member_access_identifier( + cursor_offset, line) + index = lineparts.current_indexed_member_access_identifier_with_index( + cursor_offset, line) + member = lineparts.current_indexed_member_access_member( + cursor_offset, line) + try: - locals_['temp_val_from_array'] = safe_eval(dexpr, locals_) + obj = safe_eval(arr.word, locals_) + except EvaluationError: + return None + if type(obj) not in (list, tuple) + string_types: + # then is may be unsafe to do attribute lookup on it + return None + + try: + locals_['temp_val_from_array'] = safe_eval(index.word, locals_) except (EvaluationError, IndexError): - return set() + return None - temp_line = line.replace(member_part, 'temp_val_from_array.') + temp_line = line.replace(index.word, 'temp_val_from_array.') matches = self.completer.matches(len(temp_line), temp_line, **kwargs) + if matches is None: + return None + matches_with_correct_name = \ - set(match.replace('temp_val_from_array.', member_part) for match in matches) + set(match.replace('temp_val_from_array.', index.word+'.') + for match in matches if match[20:].startswith(member.word)) del locals_['temp_val_from_array'] return matches_with_correct_name def locate(self, current_offset, line): - return lineparts.current_array_item_member_name(current_offset, line) + a = lineparts.current_indexed_member_access(current_offset, line) + return a def format(self, match): return after_last_dot(match) diff --git a/bpython/line.py b/bpython/line.py index 15495d988..17599a5e1 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -228,25 +228,38 @@ def current_string_literal_attr(cursor_offset, line): return None -current_array_with_indexer_re = LazyReCompile( - r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+)\.(.*)''') +current_indexed_member_re = LazyReCompile( + r'''([a-zA-Z_][\w.]*)\[([a-zA-Z0-9_"']+)\]\.([\w.]*)''') -def current_array_with_indexer(cursor_offset, line): - """an array and indexer, e.g. foo[1]""" - matches = current_array_with_indexer_re.finditer(line) +def current_indexed_member_access(cursor_offset, line): + """An identifier being indexed and member accessed""" + matches = current_indexed_member_re.finditer(line) for m in matches: - if m.start(1) <= cursor_offset and m.end(1) <= cursor_offset: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(1), m.end(3), m.group()) + + +def current_indexed_member_access_identifier(cursor_offset, line): + """An identifier being indexed, e.g. foo in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) + for m in matches: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) -current_array_item_member_name_re = LazyReCompile( - r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+\.)(.*)''') +def current_indexed_member_access_identifier_with_index(cursor_offset, line): + """An identifier being indexed with the index, e.g. foo[1] in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) + for m in matches: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(1), m.end(2)+1, + "%s[%s]" % (m.group(1), m.group(2))) -def current_array_item_member_name(cursor_offset, line): - """the member name after an array indexer, e.g. foo[1].bar""" - matches = current_array_item_member_name_re.finditer(line) +def current_indexed_member_access_member(cursor_offset, line): + """The member name of an indexed object, e.g. bar in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) for m in matches: - if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: - return LinePart(m.start(1), m.end(2), m.group(1)) + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(3), m.end(3), m.group(3)) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index a8ec672de..37a52002d 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -283,6 +283,32 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': [OldStyleFoo()]}), set(['a[0].method', 'a[0].a', 'a[0].b'])) + def test_other_getitem_methods_not_called(self): + class FakeList(object): + def __getitem__(inner_self, i): + self.fail("possibly side-effecting __getitem_ method called") + + self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + + def test_tuples_complete(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': (Foo(),)}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + @unittest.skip('TODO, subclasses do not complete yet') + def test_list_subclasses_complete(self): + class ListSubclass(list): pass + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': ListSubclass([Foo()])}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + def test_getitem_not_called_in_list_subclasses_overriding_getitem(self): + class FakeList(list): + def __getitem__(inner_self, i): + self.fail("possibly side-effecting __getitem_ method called") + + self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + class TestMagicMethodCompletion(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 26eee9a07..7ea49f5d8 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -5,7 +5,9 @@ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ current_method_definition_name, current_single_word, \ - current_string_literal_attr + current_string_literal_attr, current_indexed_member_access_identifier, \ + current_indexed_member_access_identifier_with_index, \ + current_indexed_member_access_member def cursor(s): @@ -20,7 +22,7 @@ def decode(s): if not s.count('|') == 1: raise ValueError('match helper needs | to occur once') - if s.count('<') != s.count('>') or not s.count('<') in (0, 1): + if s.count('<') != s.count('>') or s.count('<') not in (0, 1): raise ValueError('match helper needs <, and > to occur just once') matches = list(re.finditer(r'[<>|]', s)) assert len(matches) in [1, 3], [m.group() for m in matches] @@ -305,6 +307,40 @@ def test_simple(self): self.assertAccess('"hey".asdf d|') self.assertAccess('"hey".<|>') +class TestCurrentIndexedMemberAccessIdentifier(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_identifier + + def test_simple(self): + self.assertAccess('[def].ghi|') + self.assertAccess('[def].|ghi') + self.assertAccess('[def].gh|i') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') + + +class TestCurrentIndexedMemberAccessIdentifierWithIndex(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_identifier_with_index + + def test_simple(self): + self.assertAccess('.ghi|') + self.assertAccess('.|ghi') + self.assertAccess('.gh|i') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') + + +class TestCurrentIndexedMemberAccessMember(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_member + + def test_simple(self): + self.assertAccess('abc[def].') + self.assertAccess('abc[def].<|ghi>') + self.assertAccess('abc[def].') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') if __name__ == '__main__': unittest.main() From dc5e87fd91f3e4377f7623c2b7ed72e4b7cecc53 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 29 Nov 2015 23:29:53 +0100 Subject: [PATCH 0047/1036] Log exceptions from auto completers Also do not catch KeyboardInterrupt and SystemExit. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 762a946c0..91818d4a5 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -29,6 +29,7 @@ import abc import glob import keyword +import logging import os import re import rlcompleter @@ -609,13 +610,18 @@ def get_completer(completers, cursor_offset, line, **kwargs): double underscore methods like __len__ in method signatures """ - try: - for completer in completers: + for completer in completers: + try: matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return sorted(matches), (completer if matches else None) - except: - pass + except Exception as e: + # Instead of crashing the UI, log exceptions from autocompleters. + logger = logging.getLogger(__name__) + logger.debug( + 'Completer {} failed with unhandled exception: {}'.format( + completer, e)) + continue + if matches is not None: + return sorted(matches), (completer if matches else None) return [], None From 404e5c718ebc3e58a655f65404aef36651da7f26 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 29 Nov 2015 23:39:15 +0100 Subject: [PATCH 0048/1036] No longer install Twisted for 2.6 Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index dadbd2373..f1fd0c7f8 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -20,15 +20,12 @@ if [[ $RUN == nosetests ]]; then # Python 2.6 specific dependencies if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2 + elif [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then + # dependencies for crasher tests + pip install Twisted urwid fi case $TRAVIS_PYTHON_VERSION in - 2*) - # dependencies for crasher tests - pip install Twisted urwid - # test specific dependencies - pip install mock - ;; - pypy) + 2*|pypy) # test specific dependencies pip install mock ;; From 599cfea433f60f750a3c019add211353a7bba4bc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 29 Nov 2015 14:51:44 -0500 Subject: [PATCH 0049/1036] fix parameter name completion --- bpython/autocomplete.py | 14 +++++++------- bpython/test/test_autocomplete.py | 4 ++-- bpython/test/test_repl.py | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 91818d4a5..070aa981b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -160,17 +160,17 @@ def format(self, word): return self._completers[0].format(word) def matches(self, cursor_offset, line, **kwargs): + return_value = None all_matches = set() for completer in self._completers: - # these have to be explicitely listed to deal with the different - # signatures of various matches() methods of completers matches = completer.matches(cursor_offset=cursor_offset, line=line, **kwargs) if matches is not None: all_matches.update(matches) + return_value = all_matches - return all_matches + return return_value class ImportCompletion(BaseCompletionType): @@ -634,11 +634,11 @@ def get_default_completer(mode=SIMPLE): FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), - GlobalCompletion(mode=mode), - ArrayItemMembersCompletion(mode=mode), - CumulativeCompleter((AttrCompletion(mode=mode), + CumulativeCompleter((GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), - mode=mode) + mode=mode), + ArrayItemMembersCompletion(mode=mode), + AttrCompletion(mode=mode), ) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 37a52002d..76c519511 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -107,10 +107,10 @@ def test_one_empty_completer_returns_empty(self): cumulative = autocomplete.CumulativeCompleter([a]) self.assertEqual(cumulative.matches(3, 'abc'), set()) - def test_one_none_completer_returns_empty(self): + def test_one_none_completer_returns_none(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc'), set()) + self.assertEqual(cumulative.matches(3, 'abc'), None) def test_two_completers_get_both(self): a = self.completer(['a']) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 08ed7564c..acd3a6889 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -383,7 +383,7 @@ def test_fuzzy_attribute_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) - # 3. Edge Cases + # 3. Edge cases def test_updating_namespace_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("foo") @@ -400,6 +400,19 @@ def test_file_should_not_appear_in_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertNotIn('__file__', self.repl.matches_iter.matches) + # 4. Parameter names + def test_paremeter_name_completion(self): + self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.set_input_line("foo(ab") + + code = "def foo(abc=1, abd=2, xyz=3):\n\tpass\n" + for line in code.split("\n"): + self.repl.push(line) + + self.assertTrue(self.repl.complete()) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['abc=', 'abd=', 'abs(']) + class TestCliRepl(unittest.TestCase): From 575cafaab9bf7f0339dd1541f6c6ce8b8885d9dd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 1 Dec 2015 10:45:46 +0100 Subject: [PATCH 0050/1036] Update appdata to latest spec Signed-off-by: Sebastian Ramacher --- data/bpython.appdata.xml | 43 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml index b24c09368..e3d5d32c9 100644 --- a/data/bpython.appdata.xml +++ b/data/bpython.appdata.xml @@ -1,8 +1,8 @@ - + - - bpython.desktop + + bpython.desktop CC0-1.0 MIT bpython interpreter @@ -23,15 +23,32 @@