| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent bf87634 commit 3ee8696
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,27 +48,36 @@ | |||
| 48 | 48 | SUBSTRING = 'substring' | |
| 49 | 49 | FUZZY = 'fuzzy' | |
| 50 | 50 | ||
| 51 | - def attr_complete(text, namespace=None, config=None): | ||
| 51 | + MAGIC_METHODS = ["__%s__" % s for s in [ | ||
| 52 | + "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", | ||
| 53 | + "nonzero", "unicode", "getattr", "setattr", "get", "set","call", "len", | ||
| 54 | + "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", | ||
| 55 | + "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", | ||
| 56 | + "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", | ||
| 57 | + "oct", "hex", "index", "coerce", "enter", "exit"]] | ||
| 58 | + | ||
| 59 | + | ||
| 60 | + def attr_complete(text, namespace=None, mode=SIMPLE): | ||
| 52 | 61 | """Return list of matches """ | |
| 53 | 62 | if namespace is None: | |
| 54 | - namespace = __main__.__dict__ #TODO figure out if this __main__ still makes sense | ||
| 63 | + namespace = __main__.__dict__ | ||
| 64 | + | ||
| 65 | + assert '.' in text | ||
| 66 | + | ||
| 67 | + for i in range(1, len(text) + 1): | ||
| 68 | + if text[-i] == '[': | ||
| 69 | + i -= 1 | ||
| 70 | + break | ||
| 71 | + methodtext = text[-i:] | ||
| 72 | + matches = [''.join([text[:-i], m]) for m in | ||
| 73 | + attr_matches(methodtext, namespace, mode)] | ||
| 74 | + | ||
| 75 | + # unless the first character is a _ filter out all attributes starting with a _ | ||
| 76 | + if not text.split('.')[-1].startswith('_'): | ||
| 77 | + matches = [match for match in matches | ||
| 78 | + if not match.split('.')[-1].startswith('_')] | ||
| 79 | + return matches | ||
| 55 | 80 | ||
| 56 | - if hasattr(config, 'autocomplete_mode'): | ||
| 57 | - autocomplete_mode = config.autocomplete_mode | ||
| 58 | - else: | ||
| 59 | - autocomplete_mode = SUBSTRING | ||
| 60 | - | ||
| 61 | - if "." in text: | ||
| 62 | - # Examples: 'foo.b' or 'foo[bar.' | ||
| 63 | - for i in range(1, len(text) + 1): | ||
| 64 | - if text[-i] == '[': | ||
| 65 | - i -= 1 | ||
| 66 | - break | ||
| 67 | - methodtext = text[-i:] | ||
| 68 | - return [''.join([text[:-i], m]) for m in | ||
| 69 | - attr_matches(methodtext, namespace, autocomplete_mode)] | ||
| 70 | - else: | ||
| 71 | - return global_matches(text, namespace, autocomplete_mode) | ||
| 72 | 81 | ||
| 73 | 82 | class SafeEvalFailed(Exception): | |
| 74 | 83 | """If this object is returned, safe_eval failed""" | |
@@ -155,6 +164,8 @@ def global_matches(text, namespace, autocomplete_mode): | |||
| 155 | 164 | matches.sort() | |
| 156 | 165 | return matches | |
| 157 | 166 | ||
| 167 | + #TODO use method_match everywhere instead of startswith to implement other completion modes | ||
| 168 | + # will also need to rewrite checking mode so cseq replace doesn't happen in frontends | ||
| 158 | 169 | def method_match(word, size, text, autocomplete_mode): | |
| 159 | 170 | if autocomplete_mode == SIMPLE: | |
| 160 | 171 | return word[:size] == text | |
@@ -187,57 +198,45 @@ def last_part_of_filename(filename): | |||
| 187 | 198 | def after_last_dot(name): | |
| 188 | 199 | return name.rstrip('.').rsplit('.')[-1] | |
| 189 | 200 | ||
| 190 | - def dict_key_format(filename): | ||
| 191 | - # dictionary key suggestions | ||
| 192 | - #items = [x.rstrip(']') for x in items] | ||
| 193 | - #if current_item: | ||
| 194 | - # current_item = current_item.rstrip(']') | ||
| 195 | - pass | ||
| 196 | - | ||
| 197 | - def get_completer(cursor_offset, current_line, locals_, argspec, config, magic_methods): | ||
| 201 | + def get_completer(cursor_offset, current_line, locals_, argspec, full_code, mode, complete_magic_methods): | ||
| 198 | 202 | """Returns a list of matches and a class for what kind of completion is happening | |
| 199 | 203 | ||
| 200 | 204 | If no completion type is relevant, returns None, None""" | |
| 201 | 205 | ||
| 202 | - #TODO use the smarter current_string() in Repl that knows about the buffer | ||
| 203 | - #TODO don't pass in config, pass in the settings themselves | ||
| 206 | + kwargs = {'locals_':locals_, 'argspec':argspec, 'full_code':full_code, | ||
| 207 | + 'mode':mode, 'complete_magic_methods':complete_magic_methods} | ||
| 204 | 208 | ||
| 205 | - matches = ImportCompletion.matches(cursor_offset, current_line) | ||
| 206 | - if matches is not None: | ||
| 207 | - return sorted(set(matches)), ImportCompletion | ||
| 209 | + # mutually exclusive matchers: if one returns [], don't go on | ||
| 210 | + for completer in [ImportCompletion, FilenameCompletion, | ||
| 211 | + MagicMethodCompletion, GlobalCompletion]: | ||
| 212 | + matches = completer.matches(cursor_offset, current_line, **kwargs) | ||
| 213 | + if matches is not None: | ||
| 214 | + return sorted(set(matches)), completer | ||
| 208 | 215 | ||
| 209 | - matches = FilenameCompletion.matches(cursor_offset, current_line) | ||
| 210 | - if matches is not None: | ||
| 211 | - return sorted(set(matches)), FilenameCompletion | ||
| 216 | + # mutually exclusive if matches: If one of these returns [], try the next one | ||
| 217 | + for completer in [DictKeyCompletion]: | ||
| 218 | + matches = completer.matches(cursor_offset, current_line, **kwargs) | ||
| 219 | + if matches: | ||
| 220 | + return sorted(set(matches)), completer | ||
| 212 | 221 | ||
| 213 | - matches = DictKeyCompletion.matches(cursor_offset, current_line, locals_=locals_, config=config) | ||
| 214 | - if matches: | ||
| 215 | - return sorted(set(matches)), DictKeyCompletion | ||
| 222 | + matches = AttrCompletion.matches(cursor_offset, current_line, **kwargs) | ||
| 216 | 223 | ||
| 217 | - matches = AttrCompletion.matches(cursor_offset, current_line, locals_=locals_, config=config) | ||
| 218 | - if matches is not None: | ||
| 219 | - cw = AttrCompletion.locate(cursor_offset, current_line)[2] | ||
| 220 | - matches.extend(magic_methods(cw)) | ||
| 221 | - if argspec: | ||
| 222 | - matches.extend(name + '=' for name in argspec[1][0] | ||
| 223 | - if isinstance(name, basestring) and name.startswith(cw)) | ||
| 224 | - if py3: | ||
| 225 | - matches.extend(name + '=' for name in argspec[1][4] | ||
| 226 | - if name.startswith(cw)) | ||
| 224 | + # cumulative completions - try them all | ||
| 225 | + # They all use current_word replacement and formatting | ||
| 226 | + current_word_matches = [] | ||
| 227 | + for completer in [AttrCompletion, ParameterNameCompletion]: | ||
| 228 | + matches = completer.matches(cursor_offset, current_line, **kwargs) | ||
| 229 | + if matches is not None: | ||
| 230 | + current_word_matches.extend(matches) | ||
| 227 | 231 | ||
| 228 | - # unless the first character is a _ filter out all attributes starting with a _ | ||
| 229 | - if not cw.split('.')[-1].startswith('_'): | ||
| 230 | - matches = [match for match in matches | ||
| 231 | - if not match.split('.')[-1].startswith('_')] | ||
| 232 | - | ||
| 233 | - return sorted(set(matches)), AttrCompletion | ||
| 234 | - | ||
| 235 | - return None, None | ||
| 232 | + if len(current_word_matches) == 0: | ||
| 233 | + return None, None | ||
| 234 | + return sorted(set(current_word_matches)), AttrCompletion | ||
| 236 | 235 | ||
| 237 | 236 | ||
| 238 | 237 | class BaseCompletionType(object): | |
| 239 | 238 | """Describes different completion types""" | |
| 240 | - def matches(cls, cursor_offset, line): | ||
| 239 | + def matches(cls, cursor_offset, line, **kwargs): | ||
| 241 | 240 | """Returns a list of possible matches given a line and cursor, or None | |
| 242 | 241 | if this completion type isn't applicable. | |
| 243 | 242 | ||
@@ -268,14 +267,16 @@ def substitute(cls, cursor_offset, line, match): | |||
| 268 | 267 | return result | |
| 269 | 268 | ||
| 270 | 269 | class ImportCompletion(BaseCompletionType): | |
| 271 | - matches = staticmethod(importcompletion.complete) | ||
| 270 | + @classmethod | ||
| 271 | + def matches(cls, cursor_offset, current_line, **kwargs): | ||
| 272 | + return importcompletion.complete(cursor_offset, current_line) | ||
| 272 | 273 | locate = staticmethod(lineparts.current_word) | |
| 273 | 274 | format = staticmethod(after_last_dot) | |
| 274 | 275 | ||
| 275 | 276 | class FilenameCompletion(BaseCompletionType): | |
| 276 | 277 | shown_before_tab = False | |
| 277 | 278 | @classmethod | |
| 278 | - def matches(cls, cursor_offset, current_line): | ||
| 279 | + def matches(cls, cursor_offset, current_line, **kwargs): | ||
| 279 | 280 | cs = lineparts.current_string(cursor_offset, current_line) | |
| 280 | 281 | if cs is None: | |
| 281 | 282 | return None | |
@@ -285,19 +286,19 @@ def matches(cls, cursor_offset, current_line): | |||
| 285 | 286 | ||
| 286 | 287 | class AttrCompletion(BaseCompletionType): | |
| 287 | 288 | @classmethod | |
| 288 | - def matches(cls, cursor_offset, line, locals_, config): | ||
| 289 | + def matches(cls, cursor_offset, line, locals_, mode, **kwargs): | ||
| 289 | 290 | r = cls.locate(cursor_offset, line) | |
| 290 | 291 | if r is None: | |
| 291 | 292 | return None | |
| 292 | 293 | cw = r[2] | |
| 293 | - return attr_complete(cw, namespace=locals_, config=config) | ||
| 294 | - locate = staticmethod(lineparts.current_word) | ||
| 294 | + return attr_complete(cw, namespace=locals_, mode=mode) | ||
| 295 | + locate = staticmethod(lineparts.current_dotted_attribute) | ||
| 295 | 296 | format = staticmethod(after_last_dot) | |
| 296 | 297 | ||
| 297 | 298 | class DictKeyCompletion(BaseCompletionType): | |
| 298 | 299 | locate = staticmethod(lineparts.current_dict_key) | |
| 299 | 300 | @classmethod | |
| 300 | - def matches(cls, cursor_offset, line, locals_, config): | ||
| 301 | + def matches(cls, cursor_offset, line, locals_, **kwargs): | ||
| 301 | 302 | r = cls.locate(cursor_offset, line) | |
| 302 | 303 | if r is None: | |
| 303 | 304 | return None | |
@@ -313,3 +314,43 @@ def matches(cls, cursor_offset, line, locals_, config): | |||
| 313 | 314 | @classmethod | |
| 314 | 315 | def format(cls, match): | |
| 315 | 316 | return match[:-1] | |
| 317 | + | ||
| 318 | + class MagicMethodCompletion(BaseCompletionType): | ||
| 319 | + locate = staticmethod(lineparts.current_method_definition_name) | ||
| 320 | + @classmethod | ||
| 321 | + def matches(cls, cursor_offset, line, full_code, **kwargs): | ||
| 322 | + r = cls.locate(cursor_offset, line) | ||
| 323 | + if r is None: | ||
| 324 | + return None | ||
| 325 | + if 'class' not in full_code: | ||
| 326 | + return None | ||
| 327 | + start, end, word = r | ||
| 328 | + return [name for name in MAGIC_METHODS if name.startswith(word)] | ||
| 329 | + | ||
| 330 | + class GlobalCompletion(BaseCompletionType): | ||
| 331 | + @classmethod | ||
| 332 | + def matches(cls, cursor_offset, line, locals_, mode, **kwargs): | ||
| 333 | + r = cls.locate(cursor_offset, line) | ||
| 334 | + if r is None: | ||
| 335 | + return None | ||
| 336 | + start, end, word = r | ||
| 337 | + return global_matches(word, locals_, mode) | ||
| 338 | + locate = staticmethod(lineparts.current_single_word) | ||
| 339 | + | ||
| 340 | + class ParameterNameCompletion(BaseCompletionType): | ||
| 341 | + @classmethod | ||
| 342 | + def matches(cls, cursor_offset, line, argspec, **kwargs): | ||
| 343 | + if not argspec: | ||
| 344 | + return None | ||
| 345 | + r = cls.locate(cursor_offset, line) | ||
| 346 | + if r is None: | ||
| 347 | + return None | ||
| 348 | + start, end, word = r | ||
| 349 | + if argspec: | ||
| 350 | + matches = [name + '=' for name in argspec[1][0] | ||
| 351 | + if isinstance(name, basestring) and name.startswith(word)] | ||
| 352 | + if py3: | ||
| 353 | + matches.extend(name + '=' for name in argspec[1][4] | ||
| 354 | + if name.startswith(word)) | ||
| 355 | + return matches | ||
| 356 | + locate = staticmethod(lineparts.current_word) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,15 +6,6 @@ | |||
| 6 | 6 | from bpython.keys import cli_key_dispatch as key_dispatch | |
| 7 | 7 | from bpython.autocomplete import SIMPLE as default_completion | |
| 8 | 8 | ||
| 9 | - MAGIC_METHODS = ", ".join("__%s__" % s for s in [ | ||
| 10 | - "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", | ||
| 11 | - "nonzero", "unicode", "getattr", "setattr", "get", "set","call", "len", | ||
| 12 | - "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", | ||
| 13 | - "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", | ||
| 14 | - "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", | ||
| 15 | - "oct", "hex", "index", "coerce", "enter", "exit"] | ||
| 16 | - ) | ||
| 17 | - | ||
| 18 | 9 | class Struct(object): | |
| 19 | 10 | """Simple class for instantiating objects we can add arbitrary attributes | |
| 20 | 11 | to and use for various arbitrary things.""" | |
@@ -55,7 +46,6 @@ def loadini(struct, configfile): | |||
| 55 | 46 | 'auto_display_list': True, | |
| 56 | 47 | 'color_scheme': 'default', | |
| 57 | 48 | 'complete_magic_methods' : True, | |
| 58 | - 'magic_methods' : MAGIC_METHODS, | ||
| 59 | 49 | 'autocomplete_mode': default_completion, | |
| 60 | 50 | 'dedent_after': 1, | |
| 61 | 51 | 'flush_output': True, | |
@@ -155,8 +145,6 @@ def loadini(struct, configfile): | |||
| 155 | 145 | ||
| 156 | 146 | struct.complete_magic_methods = config.getboolean('general', | |
| 157 | 147 | 'complete_magic_methods') | |
| 158 | - methods = config.get('general', 'magic_methods') | ||
| 159 | - struct.magic_methods = [meth.strip() for meth in methods.split(",")] | ||
| 160 | 148 | struct.autocomplete_mode = config.get('general', 'autocomplete_mode') | |
| 161 | 149 | struct.save_append_py = config.getboolean('general', 'save_append_py') | |
| 162 | 150 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -125,3 +125,28 @@ def current_import(cursor_offset, line): | |||
| 125 | 125 | end = baseline.end() + m.end(1) | |
| 126 | 126 | if start < cursor_offset and end >= cursor_offset: | |
| 127 | 127 | return start, end, m.group(1) | |
| 128 | + | ||
| 129 | + def current_method_definition_name(cursor_offset, line): | ||
| 130 | + """The name of a method being defined""" | ||
| 131 | + matches = re.finditer("def\s+([a-zA-Z_][\w]*)", line) | ||
| 132 | + for m in matches: | ||
| 133 | + if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): | ||
| 134 | + return m.start(1), m.end(1), m.group(1) | ||
| 135 | + return None | ||
| 136 | + | ||
| 137 | + def current_single_word(cursor_offset, line): | ||
| 138 | + """the un-dotted word just before or under the cursor""" | ||
| 139 | + matches = re.finditer(r"(?<![.])\b([a-zA-Z_][\w]*)", line) | ||
| 140 | + for m in matches: | ||
| 141 | + if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): | ||
| 142 | + return m.start(1), m.end(1), m.group(1) | ||
| 143 | + return None | ||
| 144 | + | ||
| 145 | + def current_dotted_attribute(cursor_offset, line): | ||
| 146 | + """The dotted attribute-object pair before the cursor""" | ||
| 147 | + match = current_word(cursor_offset, line) | ||
| 148 | + if match is None: return None | ||
| 149 | + start, end, word = match | ||
| 150 | + if '.' in word[1:]: | ||
| 151 | + return start, end, word | ||
| 152 | + return None | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -601,16 +601,6 @@ def set_docstring(self): | |||
| 601 | 601 | if not self.docstring: | |
| 602 | 602 | self.docstring = None | |
| 603 | 603 | ||
| 604 | - def magic_method_completions(self, cw): | ||
| 605 | - #TODO move this to autocompletion and pass in the buffer | ||
| 606 | - if (self.config.complete_magic_methods and self.buffer and | ||
| 607 | - self.buffer[0].startswith("class ") and | ||
| 608 | - self.current_line.lstrip().startswith("def ")): | ||
| 609 | - return [name for name in self.config.magic_methods | ||
| 610 | - if name.startswith(cw)] | ||
| 611 | - else: | ||
| 612 | - return [] | ||
| 613 | - | ||
| 614 | 604 | def complete(self, tab=False): | |
| 615 | 605 | """Construct a full list of possible completions and construct and | |
| 616 | 606 | display them in a window. Also check if there's an available argspec | |
@@ -631,8 +621,10 @@ def complete(self, tab=False): | |||
| 631 | 621 | self.current_line, | |
| 632 | 622 | self.interp.locals, | |
| 633 | 623 | self.argspec, | |
| 634 | - self.config, | ||
| 635 | - self.magic_method_completions) | ||
| 624 | + '\n'.join(self.buffer + [self.current_line]), | ||
| 625 | + self.config.autocomplete_mode if hasattr(self.config, 'autocomplete_mode') else autocomplete.SIMPLE, | ||
| 626 | + self.config.complete_magic_methods) | ||
| 627 | + #TODO implement completer.shown_before_tab == False (filenames shouldn't fill screen) | ||
| 636 | 628 | ||
| 637 | 629 | if (matches is None # no completion is relevant | |
| 638 | 630 | or len(matches) == 0): # a target for completion was found | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | import re | |
| 3 | 3 | ||
| 4 | 4 | from bpython.line import current_word, current_dict_key, current_dict, current_string, current_object, current_object_attribute, current_from_import_from, current_from_import_import, current_import | |
| 5 | + from bpython.line import current_word, current_dict_key, current_dict, current_string, current_object, current_object_attribute, current_from_import_from, current_from_import_import, current_import, current_method_definition_name, current_single_word | ||
| 5 | 6 | ||
| 6 | 7 | ||
| 7 | 8 | def cursor(s): | |
@@ -236,5 +237,21 @@ def test_simple(self): | |||
| 236 | 237 | self.assertAccess('if True: import <xml.do|m.minidom>') | |
| 237 | 238 | self.assertAccess('if True: import <xml.do|m.minidom> as something') | |
| 238 | 239 | ||
| 240 | + class TestMethodDefinitionName(LineTestCase): | ||
| 241 | + def setUp(self): | ||
| 242 | + self.func = current_method_definition_name | ||
| 243 | + def test_simple(self): | ||
| 244 | + self.assertAccess('def <foo|>') | ||
| 245 | + self.assertAccess(' def bar(x, y)|:') | ||
| 246 | + self.assertAccess(' def <bar|>(x, y)') | ||
| 247 | + | ||
| 248 | + class TestMethodDefinitionName(LineTestCase): | ||
| 249 | + def setUp(self): | ||
| 250 | + self.func = current_single_word | ||
| 251 | + def test_simple(self): | ||
| 252 | + self.assertAccess('foo.bar|') | ||
| 253 | + self.assertAccess('.foo|') | ||
| 254 | + self.assertAccess(' <foo|>') | ||
| 255 | + | ||
| 239 | 256 | if __name__ == '__main__': | |
| 240 | 257 | unittest.main() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments