Skip to content

Fix 506 #525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e3dfb4f
Add Ctrl-e to complete current suggestion.
amjith Mar 13, 2015
577b9c7
use configurable keys for right arrow completion
thomasballinger Mar 13, 2015
d2287ad
Merge branch 'ctrl-e'
thomasballinger Mar 13, 2015
e83fb4a
Change default to None and set it to LOCK_EX in __init__ (fixes #509)
sebastinas Mar 15, 2015
4d26621
Move attr_{matches,lookup} to AttrCompleter
sebastinas Mar 15, 2015
5cf6f52
Makes old style classes and instances show __dict__ in the autocomplete.
Mar 15, 2015
cb90872
Adds tests to validate that __dict__ is in the autocomplete of an old
Mar 15, 2015
a82b4b5
Makes the old-style class autocomplete work in 2.6 and 3.x
Mar 15, 2015
2cfa83b
Re-introduce autocompletion modes
sebastinas Mar 16, 2015
c830c75
Update tests
sebastinas Mar 16, 2015
27b9493
Fix fuzzy completion test
sebastinas Mar 16, 2015
ad8958f
Merge remote-tracking branch 'llllllllll/old-style-class-dict-autocom…
sebastinas Mar 16, 2015
ac630cc
Move pasting logic out of Repl
sebastinas Mar 23, 2015
5cdfa51
Update translations
sebastinas Mar 23, 2015
21b9a54
Use bpython.config's getpreferredencoding
sebastinas Mar 24, 2015
38f1ed6
Remember the source of interactively defined functions
michaelmulley Mar 25, 2015
7ca380d
Move linecache code to new file, use super()
michaelmulley Mar 25, 2015
92e9b0b
No need to catch this AttributeError in test code
michaelmulley Mar 25, 2015
80765a8
Fix bpdb name
sebastinas Apr 20, 2015
bb1389a
Add Keywords
sebastinas Apr 20, 2015
a33048d
fixed bug #506
thekthuser Apr 26, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion bpython/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
"""

from __future__ import print_function
import code
import imp
import os
import sys
import code
from optparse import OptionParser, OptionGroup

from bpython import __version__
Expand Down Expand Up @@ -114,6 +115,9 @@ def exec_code(interpreter, args):
source = sourcefile.read()
old_argv, sys.argv = sys.argv, args
sys.path.insert(0, os.path.abspath(os.path.dirname(args[0])))
mod = imp.new_module('__console__')
sys.modules['__console__'] = mod
interpreter.locals = mod.__dict__
interpreter.locals['__file__'] = args[0]
interpreter.runsource(source, args[0], 'exec')
sys.argv = old_argv
178 changes: 103 additions & 75 deletions bpython/autocomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
from bpython._py3compat import py3, try_decode
from bpython.lazyre import LazyReCompile

if not py3:
from types import InstanceType, ClassType


# Autocomplete modes
SIMPLE = 'simple'
Expand All @@ -68,11 +71,32 @@ def after_last_dot(name):
return name.rstrip('.').rsplit('.')[-1]


def method_match_simple(word, size, text):
return word[:size] == text


def method_match_substring(word, size, text):
return text in word


def method_match_fuzzy(word, size, text):
s = r'.*%s.*' % '.*'.join(list(text))
return re.search(s, word)


MODES_MAP = {
SIMPLE: method_match_simple,
SUBSTRING: method_match_substring,
FUZZY: method_match_fuzzy
}


class BaseCompletionType(object):
"""Describes different completion types"""

def __init__(self, shown_before_tab=True):
def __init__(self, shown_before_tab=True, mode=SIMPLE):
self._shown_before_tab = shown_before_tab
self.method_match = MODES_MAP[mode]

def matches(self, cursor_offset, line, **kwargs):
"""Returns a list of possible matches given a line and cursor, or None
Expand Down Expand Up @@ -112,17 +136,20 @@ def shown_before_tab(self):
once that has happened."""
return self._shown_before_tab

def method_match(self, word, size, text):
return word[:size] == text


class CumulativeCompleter(BaseCompletionType):
"""Returns combined matches from several completers"""

def __init__(self, completers):
def __init__(self, completers, mode=SIMPLE):
if not completers:
raise ValueError(
"CumulativeCompleter requires at least one completer")
self._completers = completers

super(CumulativeCompleter, self).__init__(True)
super(CumulativeCompleter, self).__init__(True, mode)

def locate(self, current_offset, line):
return self._completers[0].locate(current_offset, line)
Expand Down Expand Up @@ -158,8 +185,8 @@ def format(self, word):

class FilenameCompletion(BaseCompletionType):

def __init__(self):
super(FilenameCompletion, self).__init__(False)
def __init__(self, mode=SIMPLE):
super(FilenameCompletion, self).__init__(False, mode)

if sys.version_info[:2] >= (3, 4):
def safe_glob(self, pathname):
Expand Down Expand Up @@ -199,6 +226,9 @@ def format(self, filename):
return filename


attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)")


class AttrCompletion(BaseCompletionType):

def matches(self, cursor_offset, line, **kwargs):
Expand All @@ -222,7 +252,7 @@ def matches(self, cursor_offset, line, **kwargs):
break
methodtext = text[-i:]
matches = set(''.join([text[:-i], m])
for m in attr_matches(methodtext, locals_))
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
Expand All @@ -238,6 +268,55 @@ def locate(self, current_offset, line):
def format(self, word):
return after_last_dot(word)

def attr_matches(self, text, namespace):
"""Taken from rlcompleter.py and bent to my will.
"""

# Gna, Py 2.6's rlcompleter searches for __call__ inside the
# instance instead of the type, so we monkeypatch to prevent
# side-effects (__getattr__/__getattribute__)
m = attr_matches_re.match(text)
if not m:
return []

expr, attr = m.group(1, 3)
if expr.isdigit():
# Special case: float literal, using attrs here will result in
# a SyntaxError
return []
try:
obj = safe_eval(expr, namespace)
except EvaluationError:
return []
with inspection.AttrCleaner(obj):
matches = self.attr_lookup(obj, expr, attr)
return matches

def attr_lookup(self, obj, expr, attr):
"""Second half of original attr_matches method factored out so it can
be wrapped in a safe try/finally block in case anything bad happens to
restore the original __getattribute__ method."""
words = dir(obj)
if hasattr(obj, '__class__'):
words.append('__class__')
words = words + rlcompleter.get_class_members(obj.__class__)
if not isinstance(obj.__class__, abc.ABCMeta):
try:
words.remove('__abstractmethods__')
except ValueError:
pass

if not py3 and isinstance(obj, (InstanceType, ClassType)):
# Account for the __dict__ in an old-style class.
words.append('__dict__')

matches = []
n = len(attr)
for word in words:
if self.method_match(word, n, attr) and word != "__builtins__":
matches.append("%s.%s" % (expr, word))
return matches


class DictKeyCompletion(BaseCompletionType):

Expand Down Expand Up @@ -306,11 +385,11 @@ def matches(self, cursor_offset, line, **kwargs):
matches = set()
n = len(text)
for word in KEYWORDS:
if method_match(word, n, text):
if self.method_match(word, n, text):
matches.add(word)
for nspace in (builtins.__dict__, locals_):
for word, val in iteritems(nspace):
if method_match(word, n, text) and word != "__builtins__":
if self.method_match(word, n, text) and word != "__builtins__":
word = try_decode(word, 'ascii')
# if identifier isn't ascii, don't complete (syntax error)
if word is None:
Expand Down Expand Up @@ -460,28 +539,31 @@ def get_completer(completers, cursor_offset, line, **kwargs):
"""

for completer in completers:
matches = completer.matches(
cursor_offset, line, **kwargs)
matches = completer.matches(cursor_offset, line, **kwargs)
if matches is not None:
return sorted(matches), (completer if matches else None)
return [], None


BPYTHON_COMPLETER = (
DictKeyCompletion(),
StringLiteralAttrCompletion(),
ImportCompletion(),
FilenameCompletion(),
MagicMethodCompletion(),
MultilineJediCompletion(),
GlobalCompletion(),
CumulativeCompleter((AttrCompletion(), ParameterNameCompletion()))
)
def get_default_completer(mode=SIMPLE):
return (
DictKeyCompletion(mode=mode),
StringLiteralAttrCompletion(mode=mode),
ImportCompletion(mode=mode),
FilenameCompletion(mode=mode),
MagicMethodCompletion(mode=mode),
MultilineJediCompletion(mode=mode),
GlobalCompletion(mode=mode),
CumulativeCompleter((AttrCompletion(mode=mode),
ParameterNameCompletion(mode=mode)),
mode=mode)
)


def get_completer_bpython(cursor_offset, line, **kwargs):
""""""
return get_completer(BPYTHON_COMPLETER, cursor_offset, line, **kwargs)
return get_completer(get_default_completer(),
cursor_offset, line, **kwargs)


class EvaluationError(Exception):
Expand All @@ -498,63 +580,9 @@ def safe_eval(expr, namespace):
raise EvaluationError


attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)")


def attr_matches(text, namespace):
"""Taken from rlcompleter.py and bent to my will.
"""

# Gna, Py 2.6's rlcompleter searches for __call__ inside the
# instance instead of the type, so we monkeypatch to prevent
# side-effects (__getattr__/__getattribute__)
m = attr_matches_re.match(text)
if not m:
return []

expr, attr = m.group(1, 3)
if expr.isdigit():
# Special case: float literal, using attrs here will result in
# a SyntaxError
return []
try:
obj = safe_eval(expr, namespace)
except EvaluationError:
return []
with inspection.AttrCleaner(obj):
matches = attr_lookup(obj, expr, attr)
return matches


def attr_lookup(obj, expr, attr):
"""Second half of original attr_matches method factored out so it can
be wrapped in a safe try/finally block in case anything bad happens to
restore the original __getattribute__ method."""
words = dir(obj)
if hasattr(obj, '__class__'):
words.append('__class__')
words = words + rlcompleter.get_class_members(obj.__class__)
if not isinstance(obj.__class__, abc.ABCMeta):
try:
words.remove('__abstractmethods__')
except ValueError:
pass

matches = []
n = len(attr)
for word in words:
if method_match(word, n, attr) and word != "__builtins__":
matches.append("%s.%s" % (expr, word))
return matches


def _callable_postfix(value, word):
"""rlcompleter's _callable_postfix done right."""
with inspection.AttrCleaner(value):
if inspection.is_callable(value):
word += '('
return word


def method_match(word, size, text):
return word[:size] == text
10 changes: 6 additions & 4 deletions bpython/curtsiesfrontend/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,12 +594,14 @@ def process_key_event(self, e):
if self.stdin.has_focus:
return self.stdin.process_event(e)

if (e in ("<RIGHT>", '<Ctrl-f>') and
self.config.curtsies_right_arrow_completion and
self.cursor_offset == len(self.current_line)):
if (e in (key_dispatch[self.config.right_key] +
key_dispatch[self.config.end_of_line_key] +
("<RIGHT>",))
and self.config.curtsies_right_arrow_completion
and self.cursor_offset == len(self.current_line)):

self.current_line += self.current_suggestion
self.cursor_offset = len(self.current_line)

elif e in ("<UP>",) + key_dispatch[self.config.up_one_line_key]:
self.up_one_line()
elif e in ("<DOWN>",) + key_dispatch[self.config.down_one_line_key]:
Expand Down
5 changes: 4 additions & 1 deletion bpython/filelock.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ class FileLock(object):
On platforms without fcntl, all operations in this class are no-ops.
"""

def __init__(self, fd, mode=fcntl.LOCK_EX):
def __init__(self, fd, mode=None):
if has_fcntl and mode is None:
mode = fcntl.LOCK_EX

self.fd = fd
self.mode = mode

Expand Down
Loading