Jason Laster avatar Jason Laster committed 93f2cea

update tab method to support new complete types

Comments (0)

Files changed (5)

bpython/autocomplete.py

 import re
 from bpython import inspection
 
-
+# Needed for special handling of __abstractmethods__
+# abc only exists since 2.6, so check both that it exists and that it's
+# the one we're expecting
+try:
+    import abc
+    abc.ABCMeta
+    has_abc = True
+except (ImportError, AttributeError):
+    has_abc = False
 
 class Autocomplete(rlcompleter.Completer):
     """
         for word in keyword.kwlist:
             if self.method_match(word, n, text):
                 hash[word] = 1
-        for nspace in [__builtin__.__dict__, __main__.__dict__]:
+        for nspace in [__builtin__.__dict__, self.namespace]:
             for word, val in nspace.items():
                 if self.method_match(word, len(text), text) and word != "__builtins__":
                     hash[self._callable_postfix(val, word)] = 1
         return matches
 
     def method_match(self, word, size, text):
-        if self.autocomplete_mode == "1":
+        if self.autocomplete_mode == 1:
             return word[:size] == text
+        elif self.autocomplete_mode == 2:
+            s = r'.*%s.*' % text
+            return re.search(s, word)
         else:
             s = r'.*%s.*' % '.*'.join(list(text))
             return re.search(s, word)
         """Initialise the repl and jump into the loop. This method also has to
         keep a stack of lines entered for the horrible "undo" feature. It also
         tracks everything that would normally go to stdout in the normal Python
-        interpreter so it can quickly write it to stdout on exit after
+        interpreter so it can quickly write it to st on exit after
         curses.endwin(), as well as a history of lines entered for using
         up/down to go back and forth (which has to be separate to the
         evaluation history, which will be truncated when undoing."""
         and don't indent if there are only whitespace in the line.
         """
 
+        mode = self.config.autocomplete_mode
+
         # 1. check if we should add a tab character
         if self.atbol() and not back:
             x_pos = len(self.s) - self.cpos
         else:
             cw = self.matches_iter.current_word
 
-        # check to see if we can expand the current word
-        b = os.path.commonprefix(self.matches)
-        if b and self.config.autocomplete_mode == 1:
-            expanded_string = b[len(cw):]
+        # 3. check to see if we can expand the current word
+        cseq = None
+        if mode == 2:
+            if all([len(match.split(cw)) == 2 for match in self.matches]):
+                seq = [cw + match.split(cw)[1] for match in self.matches]
+                cseq = os.path.commonprefix(seq)
+        else:
+            seq = self.matches
+            cseq = os.path.commonprefix(seq)
+
+        if cseq and mode != 3:
+            expanded_string = cseq[len(cw):]
             self.s += expanded_string
             expanded = bool(expanded_string)
             self.print_line(self.s)
             if len(self.matches) == 1 and self.config.auto_display_list:
                 self.scr.touchwin()
             if expanded:
-                self.matches_iter.update(b, self.matches)
+                self.matches_iter.update(cseq, self.matches)
         else:
             expanded = False
 
-        # swap current word for a match list item
+        # 4. swap current word for a match list item
         if not expanded and self.matches:
             # reset s if this is the nth result
             if self.matches_iter:
                 if self.config.autocomplete_mode == 1:
                     self.s += current_match[len(cw):]
                 else:
-                    self.s = current_match
+                    self.s = self.s[:-len(cw)] + current_match
 
                 self.print_line(self.s, True)
         return True

bpython/config.py

                                                       'complete_magic_methods')
     methods = config.get('general', 'magic_methods')
     struct.magic_methods = [meth.strip() for meth in methods.split(",")]
-    struct.autocomplete_mode = config.get('general', 'autocomplete_mode')
+    struct.autocomplete_mode = config.getint('general', 'autocomplete_mode')
 
     struct.gtk_font = config.get('gtk', 'font')
 
 from bpython.formatter import Parenthesis
 from bpython.autocomplete import Autocomplete
 
-# Needed for special handling of __abstractmethods__
-# abc only exists since 2.6, so check both that it exists and that it's
-# the one we're expecting
-try:
-    import abc
-    abc.ABCMeta
-    has_abc = True
-except (ImportError, AttributeError):
-    has_abc = False
-
 py3 = sys.version_info[0] == 3
 
 

bpython/test/test_repl.py

     config.loadini(config_struct, os.devnull)
     if 'autocomplete_mode' in conf:
         config_struct.autocomplete_mode = conf['autocomplete_mode']
+    print config_struct
     return config_struct
 
+class FakeHistory(repl.History):
+
+    def __init__(self):
+        pass
+
+    def reset(self):
+        pass
+
+class FakeRepl(repl.Repl):
+    def __init__(self, conf={}):
+        repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf))
+        self.input_line = ""
+        self.current_word = ""
+        self.cpos = 0
+
+    def current_line(self):
+        return self.input_line
+
+    def cw(self):
+        return self.current_word
+
+class FakeCliRepl(cli.CLIRepl, FakeRepl):
+    def __init__(self):
+        self.s = ''
+        self.cpos = 0
+        self.rl_history = FakeHistory()
+
 class TestHistory(unittest.TestCase):
     def setUp(self):
         self.history = repl.History('#%d' % x for x in range(1000))
         self.assertNotEqual(list(slice), self.matches)
         self.assertEqual(list(newslice), newmatches)
 
-class FakeHistory(repl.History):
-
-    def __init__(self):
-        pass
-
-    def reset(self):
-        pass
-
-class FakeRepl(repl.Repl):
-    def __init__(self, conf={}):
-        repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf))
-        self.input_line = ""
-        self.current_word = ""
-        self.cpos = 0
-
-    def current_line(self):
-        return self.input_line
-
-    def cw(self):
-        return self.current_word
-
-class FakeCliRepl(cli.CLIRepl, FakeRepl):
-    def __init__(self):
-        self.s = ''
-        self.cpos = 0
-        self.rl_history = FakeHistory()
-
 class TestArgspec(unittest.TestCase):
     def setUp(self):
         self.repl = FakeRepl()
         self.repl.input_line = 'a = "2" + 2'
         self.assertEqual(self.repl.current_string(), '')
 
-    def test_default_complete(self):
-        self.repl = FakeRepl({'autocomplete_mode':"1"})
+    # TODO: figure out how to capture whether foobar is in globals
+    @unittest.skip('not working yet')
+    def test_push(self):
+        self.repl = FakeRepl()
+        self.repl.push("foobar = 2")
+        self.repl.push("\"foobar\" in globals().keys()")
+
+    # COMPLETE TESTS
+    # 1. Global tests
+    def test_simple_global_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':1})
         self.repl.input_line = "d"
         self.repl.current_word = "d"
 
         self.assertEqual(self.repl.completer.matches,
             ['def', 'del', 'delattr(', 'dict(', 'dir(', 'divmod('])
 
-    def test_alternate_complete(self):
-        self.repl = FakeRepl({'autocomplete_mode':"2"})
+    def test_substring_global_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':2})
+        self.repl.input_line = "time"
+        self.repl.current_word = "time"
+
+        self.assertTrue(self.repl.complete())
+        self.assertTrue(hasattr(self.repl.completer,'matches'))
+        self.assertEqual(self.repl.completer.matches,
+            ['RuntimeError(', 'RuntimeWarning('])
+
+    def test_fuzzy_global_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':3})
         self.repl.input_line = "doc"
         self.repl.current_word = "doc"
 
         self.assertEqual(self.repl.completer.matches,
             ['UnboundLocalError(', '__doc__'])
 
+    # 2. Attribute tests
+    def test_simple_attribute_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':1})
+        self.repl.input_line = "Foo.b"
+        self.repl.current_word = "Foo.b"
+
+        code = "class Foo():\n\tdef bar(self):\n\t\tpass\n"
+        for line in code.split("\n"):
+            self.repl.push(line)
+
+        self.assertTrue(self.repl.complete())
+        self.assertTrue(hasattr(self.repl.completer,'matches'))
+        self.assertEqual(self.repl.completer.matches,
+            ['Foo.bar'])
+
+    def test_substring_attribute_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':2})
+        self.repl.input_line = "Foo.ar"
+        self.repl.current_word = "Foo.ar"
+
+        code = "class Foo():\n\tdef bar(self):\n\t\tpass\n"
+        for line in code.split("\n"):
+            self.repl.push(line)
+
+        self.assertTrue(self.repl.complete())
+        self.assertTrue(hasattr(self.repl.completer,'matches'))
+        self.assertEqual(self.repl.completer.matches,
+            ['Foo.bar'])
+
+    def test_fuzzy_attribute_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':3})
+        self.repl.input_line = "Foo.br"
+        self.repl.current_word = "Foo.br"
+
+        code = "class Foo():\n\tdef bar(self):\n\t\tpass\n"
+        for line in code.split("\n"):
+            self.repl.push(line)
+
+        self.assertTrue(self.repl.complete())
+        self.assertTrue(hasattr(self.repl.completer,'matches'))
+        self.assertEqual(self.repl.completer.matches,
+            ['Foo.bar'])
+
+    # 3. Edge Cases
+    def test_updating_namespace_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':1})
+        self.repl.input_line = "foo"
+        self.repl.current_word = "foo"
+        self.repl.push("foobar = 2")
+
+        self.assertTrue(self.repl.complete())
+        self.assertTrue(hasattr(self.repl.completer,'matches'))
+        self.assertEqual(self.repl.completer.matches,
+            ['foobar'])
+
+    def test_file_should_not_appear_in_complete(self):
+        self.repl = FakeRepl({'autocomplete_mode':1})
+        self.repl.input_line = "_"
+        self.repl.current_word = "_"
+        self.assertTrue(self.repl.complete())
+        self.assertTrue(hasattr(self.repl.completer,'matches'))
+        self.assertNotIn('__file__', self.repl.completer.matches)
+
+
 class TestCliRepl(unittest.TestCase):
 
     def setUp(self):
         self.repl.s = s
         self.assertEqual(self.repl.cw(), s.lstrip())
 
-        self.repl.s = "this.is.\ta.test"
-        self.assertEqual(self.repl.cw(), 'a.test')
-
+        self.repl.s = "import datetime"
+        self.assertEqual(self.repl.cw(), 'datetime')
 
 class TestCliReplTab(unittest.TestCase):
 
     def setUp(self):
 
         def setup_matches(tab=False):
-            self.repl.matches = ["foobar", "foofoobar"]
+
+            if self.repl.cw() and len(self.repl.cw().split('.')) == 1:
+                self.repl.matches = ["foobar", "foofoobar"]
+            else:
+                self.repl.matches = ["Foo.foobar", "Foo.foofoobar"]
+
             self.repl.matches_iter = repl.MatchesIterator()
-            self.repl.matches_iter.update('f', self.repl.matches)
+            self.repl.matches_iter.update(self.repl.cw(), self.repl.matches)
 
         self.repl = FakeCliRepl()
 
         self.repl.config.list_win_visible = True
         self.repl.config.autocomplete_mode = 1
 
+    # 3 Types of tab complete
+    def test_simple_tab_complete(self):
+        self.repl.s = "foo"
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "foobar")
 
-    def test_tab(self):
-        def test_normal_tab(self):
-            self.repl.s = ""
-            setup_complete()
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "    ")
+    def test_substring_tab_complete(self):
+        self.repl.s = "bar"
+        self.repl.config.autocomplete_mode = 3
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "foobar")
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "foofoobar")
 
-        def test_expand(self):
-            self.repl.s = "f"
-            setup_complete()
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "foo")
+    def test_fuzzy_tab_complete(self):
+        self.repl.s = "br"
+        self.repl.config.autocomplete_mode = 3
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "foobar")
 
-        def test_first_forward(self):
-            self.repl.s = "foo"
-            setup_complete()
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "foobar")
+    # Edge Cases
+    def test_normal_tab(self):
+        """make sure pressing the tab key will
+           still in some cases add a tab"""
+        self.repl.s = ""
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "    ")
 
-        def test_first_back(self):
-            self.repl.s = "foo"
-            setup_complete()
-            self.repl.tab(back=True)
-            self.assertEqual(self.repl.s, "foofoobar")
+    def test_back_parameter(self):
+        self.repl.s = "foo"
+        self.repl.tab(back=True)
+        self.assertEqual(self.repl.s, "foofoobar")
 
-        def test_nth_forward(self):
-            self.repl.s = "f"
-            setup_complete()
-            self.repl.tab()
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "foobar")
+    def test_nth_forward(self):
+        """make sure that pressing tab twice will fist expand 
+        and then cycle to the first match"""
+        self.repl.s = "f"
+        self.repl.tab()
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "foobar")
 
-        def test_nth_back(self):
-            self.repl.s = "f"
-            setup_complete()
-            self.repl.tab()
-            self.repl.tab(back=True)
-            self.assertEqual(self.repl.s, "foofoobar")
+    def test_current_word(self):
+        """Complete should not be affected by words that precede it."""
+        self.repl.s = "import f"
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "import foo")
 
-        def test_non_contiguous_tab_complete(self):
-            self.repl.s = "br"
-            self.repl.config.autocomplete_mode = 2
-            setup_complete()
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "foobar")
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "import foobar")
 
-        def test_non_appending_tab_complete(self):
-            self.repl.s = "bar"
-            self.repl.config.autocomplete_mode = 2
-            setup_complete()
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "foobar")
-            self.repl.tab()
-            self.assertEqual(self.repl.s, "foofoobar")
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "import foofoobar")
+
+    # Attribute Tests
+    def test_fuzzy_attribute_tab_complete(self):
+        """Test fuzzy attribute with no text"""
+        self.repl.s = "Foo."
+        self.repl.config.autocomplete_mode = 3
+
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "Foo.foobar")
+        print self.repl.s
+
+    def test_fuzzy_attribute_tab_complete2(self):
+        """Test fuzzy attribute with some text"""
+        self.repl.s = "Foo.br"
+        self.repl.config.autocomplete_mode = 3
+
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "Foo.foobar")
+
+    # Expand Tests
+    def test_simple_expand(self):
+        self.repl.s = "f"
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "foo")
+
+    def test_substring_expand_forward(self):
+        self.repl.config.autocomplete_mode = 2
+        self.repl.s = "ba"
+        self.repl.tab()
+        self.assertEqual(self.repl.s, "bar")
+
+    def test_fuzzy_expand(self):
+        pass
+
+
 
 if __name__ == '__main__':
     unittest.main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.