From 52a2b412601426358f1f37845c0302afa921ed54 2010-08-25 03:38:17 From: Fernando Perez Date: 2010-08-25 03:38:17 Subject: [PATCH] Improvements to tab completion in Qt GUI with new api. All tests in main test suite pass at the moment. --- diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 155f363..247f9d4 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -193,7 +193,7 @@ class CompletionSplitter(object): class Completer(object): - def __init__(self,namespace=None,global_namespace=None): + def __init__(self, namespace=None, global_namespace=None): """Create a new completer for the command line. Completer([namespace,global_namespace]) -> completer instance. @@ -343,10 +343,12 @@ class IPCompleter(Completer): without readline, though in that case callers must provide some extra information on each call about the current line.""" - Completer.__init__(self,namespace,global_namespace) + Completer.__init__(self, namespace, global_namespace) self.magic_escape = ESC_MAGIC + self.splitter = CompletionSplitter() + # Readline-dependent code self.use_readline = use_readline if use_readline: @@ -662,16 +664,20 @@ class IPCompleter(Completer): return None - def complete(self, text, line_buffer, cursor_pos=None): + def complete(self, text=None, line_buffer=None, cursor_pos=None): """Return the state-th possible completion for 'text'. This is called successively with state == 0, 1, 2, ... until it returns None. The completion should begin with 'text'. + Note that both the text and the line_buffer are optional, but at least + one of them must be given. + Parameters ---------- - text : string - Text to perform the completion on. + text : string, optional + Text to perform the completion on. If not given, the line buffer + is split using the instance's CompletionSplitter object. line_buffer : string, optional If not given, the completer attempts to obtain the current line @@ -683,7 +689,20 @@ class IPCompleter(Completer): Index of the cursor in the full line buffer. Should be provided by remote frontends where kernel has no access to frontend state. """ - #io.rprint('COMP', text, line_buffer, cursor_pos) # dbg + #io.rprint('COMP1 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg + + # if the cursor position isn't given, the only sane assumption we can + # make is that it's at the end of the line (the common case) + if cursor_pos is None: + cursor_pos = len(line_buffer) if text is None else len(text) + + # if text is either None or an empty string, rely on the line buffer + if not text: + text = self.splitter.split_line(line_buffer, cursor_pos) + + # If no line buffer is given, assume the input text is all there was + if line_buffer is None: + line_buffer = text magic_escape = self.magic_escape self.full_lbuf = line_buffer @@ -692,6 +711,8 @@ class IPCompleter(Completer): if text.startswith('~'): text = os.path.expanduser(text) + #io.rprint('COMP2 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg + # Start with a clean slate of completions self.matches[:] = [] custom_res = self.dispatch_custom_completer(text) @@ -716,8 +737,8 @@ class IPCompleter(Completer): # simply collapse the dict into a list for readline, but we'd have # richer completion semantics in other evironments. self.matches = sorted(set(self.matches)) - #io.rprint('MATCHES', self.matches) # dbg - return self.matches + #io.rprint('COMP TEXT, MATCHES: %r, %r' % (text, self.matches)) # dbg + return text, self.matches def rlcomplete(self, text, state): """Return the state-th possible completion for 'text'. diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index debf617..cb2150d 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1370,13 +1370,15 @@ class InteractiveShell(Configurable, Magic): #------------------------------------------------------------------------- def complete(self, text, line=None, cursor_pos=None): - """Return a sorted list of all possible completions on text. + """Return the completed text and a list of completions. Parameters ---------- text : string - A string of text to be completed on. + A string of text to be completed on. It can be given as empty and + instead a line/position pair are given. In this case, the + completer itself will split the line like readline does. line : string, optional The complete line that text is part of. @@ -1384,6 +1386,14 @@ class InteractiveShell(Configurable, Magic): cursor_pos : int, optional The position of the cursor on the input line. + Returns + ------- + text : string + The actual text that was completed. + + matches : list + A sorted list with all possible completions. + The optional arguments allow the completion to take more context into account, and are part of the low-level completion API. @@ -1394,23 +1404,17 @@ class InteractiveShell(Configurable, Magic): Simple usage example: - In [7]: x = 'hello' - - In [8]: x - Out[8]: 'hello' - - In [9]: print x - hello + In [1]: x = 'hello' - In [10]: _ip.complete('x.l') - Out[10]: ['x.ljust', 'x.lower', 'x.lstrip'] + In [2]: _ip.complete('x.l') + Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip']) """ # Inject names into __builtin__ so we can complete on the added names. with self.builtin_trap: - return self.Completer.complete(text,line_buffer=text) + return self.Completer.complete(text, line, cursor_pos) - def set_custom_completer(self,completer,pos=0): + def set_custom_completer(self, completer, pos=0): """Adds a new custom completer function. The position argument (defaults to 0) is the index in the completers diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 8854b18..5097d9b 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -69,10 +69,9 @@ class CompletionSplitterTestCase(unittest.TestCase): self.sp = completer.CompletionSplitter() def test_delim_setting(self): - self.sp.delims = ' ' - # Validate that property handling works ok - nt.assert_equal(self.sp.delims, ' ') - nt.assert_equal(self.sp.delim_expr, '[\ ]') + self.sp.set_delims(' ') + nt.assert_equal(self.sp.get_delims(), ' ') + nt.assert_equal(self.sp._delim_expr, '[\ ]') def test_spaces(self): """Test with only spaces as split chars.""" diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 5a5f0f6..d004452 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -181,7 +181,10 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): cursor = self._get_cursor() if rep['parent_header']['msg_id'] == self._complete_id and \ cursor.position() == self._complete_pos: - text = '.'.join(self._get_context()) + # The completer tells us what text was actually used for the + # matching, so we must move that many characters left to apply the + # completions. + text = rep['content']['matched_text'] cursor.movePosition(QtGui.QTextCursor.Left, n=len(text)) self._complete_with_items(cursor, rep['content']['matches']) @@ -294,14 +297,22 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ Performs completion at the current cursor location. """ # Decide if it makes sense to do completion - context = self._get_context() - if not context: + + # We should return only if the line is empty. Otherwise, let the + # kernel split the line up. + line = self._get_input_buffer_cursor_line() + if not line: return False + # We let the kernel split the input line, so we *always* send an empty + # text field. Readline-based frontends do get a real text field which + # they can use. + text = '' + # Send the completion request to the kernel self._complete_id = self.kernel_manager.xreq_channel.complete( - '.'.join(context), # text - self._get_input_buffer_cursor_line(), # line + text, # text + line, # line self._get_input_buffer_cursor_column(), # cursor_pos self.input_buffer) # block self._complete_pos = self._get_cursor().position()