##// END OF EJS Templates
Add line splitting for text completion (like readline does internally)....
Fernando Perez -
Show More
@@ -61,6 +61,7 b' used, and this module (and the readline module) are silently inactive.'
61 # the file COPYING, distributed as part of this software.
61 # the file COPYING, distributed as part of this software.
62 #
62 #
63 #*****************************************************************************
63 #*****************************************************************************
64 from __future__ import print_function
64
65
65 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
66 # Imports
67 # Imports
@@ -79,7 +80,7 b' import sys'
79
80
80 from IPython.core.error import TryNext
81 from IPython.core.error import TryNext
81 from IPython.core.prefilter import ESC_MAGIC
82 from IPython.core.prefilter import ESC_MAGIC
82 from IPython.utils import generics
83 from IPython.utils import generics, io
83 from IPython.utils.frame import debugx
84 from IPython.utils.frame import debugx
84 from IPython.utils.dir2 import dir2
85 from IPython.utils.dir2 import dir2
85
86
@@ -138,9 +139,60 b' def single_dir_expand(matches):'
138 else:
139 else:
139 return matches
140 return matches
140
141
141 class Bunch: pass
142
142
143 class Completer:
143 class Bunch(object): pass
144
145
146 class CompletionSplitter(object):
147 """An object to split an input line in a manner similar to readline.
148
149 By having our own implementation, we can expose readline-like completion in
150 a uniform manner to all frontends. This object only needs to be given the
151 line of text to be split and the cursor position on said line, and it
152 returns the 'word' to be completed on at the cursor after splitting the
153 entire line.
154
155 What characters are used as splitting delimiters can be controlled by
156 setting the `delims` attribute (this is a property that internally
157 automatically builds the necessary """
158
159 # Private interface
160
161 # A string of delimiter characters. The default value makes sense for
162 # IPython's most typical usage patterns.
163 _delims = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
164
165 # The expression (a normal string) to be compiled into a regular expression
166 # for actual splitting. We store it as an attribute mostly for ease of
167 # debugging, since this type of code can be so tricky to debug.
168 _delim_expr = None
169
170 # The regular expression that does the actual splitting
171 _delim_re = None
172
173 def __init__(self, delims=None):
174 delims = CompletionSplitter._delims if delims is None else delims
175 self.set_delims(delims)
176
177 def set_delims(self, delims):
178 """Set the delimiters for line splitting."""
179 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
180 self._delim_re = re.compile(expr)
181 self._delims = delims
182 self._delim_expr = expr
183
184 def get_delims(self):
185 """Return the string of delimiter characters."""
186 return self._delims
187
188 def split_line(self, line, cursor_pos=None):
189 """Split a line of text with a cursor at the given position.
190 """
191 l = line if cursor_pos is None else line[:cursor_pos]
192 return self._delim_re.split(l)[-1]
193
194
195 class Completer(object):
144 def __init__(self,namespace=None,global_namespace=None):
196 def __init__(self,namespace=None,global_namespace=None):
145 """Create a new completer for the command line.
197 """Create a new completer for the command line.
146
198
@@ -631,7 +683,8 b' class IPCompleter(Completer):'
631 Index of the cursor in the full line buffer. Should be provided by
683 Index of the cursor in the full line buffer. Should be provided by
632 remote frontends where kernel has no access to frontend state.
684 remote frontends where kernel has no access to frontend state.
633 """
685 """
634
686 #io.rprint('COMP', text, line_buffer, cursor_pos) # dbg
687
635 magic_escape = self.magic_escape
688 magic_escape = self.magic_escape
636 self.full_lbuf = line_buffer
689 self.full_lbuf = line_buffer
637 self.lbuf = self.full_lbuf[:cursor_pos]
690 self.lbuf = self.full_lbuf[:cursor_pos]
@@ -663,7 +716,7 b' class IPCompleter(Completer):'
663 # simply collapse the dict into a list for readline, but we'd have
716 # simply collapse the dict into a list for readline, but we'd have
664 # richer completion semantics in other evironments.
717 # richer completion semantics in other evironments.
665 self.matches = sorted(set(self.matches))
718 self.matches = sorted(set(self.matches))
666 #from IPython.utils.io import rprint; rprint(self.matches) # dbg
719 #io.rprint('MATCHES', self.matches) # dbg
667 return self.matches
720 return self.matches
668
721
669 def rlcomplete(self, text, state):
722 def rlcomplete(self, text, state):
@@ -679,15 +732,15 b' class IPCompleter(Completer):'
679
732
680 state : int
733 state : int
681 Counter used by readline.
734 Counter used by readline.
682
683 """
735 """
684
685 #print "rlcomplete! '%s' %s" % (text, state) # dbg
686
687 if state==0:
736 if state==0:
737
688 self.full_lbuf = line_buffer = self.get_line_buffer()
738 self.full_lbuf = line_buffer = self.get_line_buffer()
689 cursor_pos = self.get_endidx()
739 cursor_pos = self.get_endidx()
690
740
741 #io.rprint("\nRLCOMPLETE: %r %r %r" %
742 # (text, line_buffer, cursor_pos) ) # dbg
743
691 # if there is only a tab on a line with only whitespace, instead of
744 # if there is only a tab on a line with only whitespace, instead of
692 # the mostly useless 'do you want to see all million completions'
745 # the mostly useless 'do you want to see all million completions'
693 # message, just do the right thing and give the user his tab!
746 # message, just do the right thing and give the user his tab!
@@ -699,7 +752,7 b' class IPCompleter(Completer):'
699
752
700 # don't apply this on 'dumb' terminals, such as emacs buffers, so
753 # don't apply this on 'dumb' terminals, such as emacs buffers, so
701 # we don't interfere with their own tab-completion mechanism.
754 # we don't interfere with their own tab-completion mechanism.
702 if not (self.dumb_terminal or self.full_lbuf.strip()):
755 if not (self.dumb_terminal or line_buffer.strip()):
703 self.readline.insert_text('\t')
756 self.readline.insert_text('\t')
704 sys.stdout.flush()
757 sys.stdout.flush()
705 return None
758 return None
@@ -719,4 +772,3 b' class IPCompleter(Completer):'
719 return self.matches[state]
772 return self.matches[state]
720 except IndexError:
773 except IndexError:
721 return None
774 return None
722
@@ -6,6 +6,7 b''
6
6
7 # stdlib
7 # stdlib
8 import sys
8 import sys
9 import unittest
9
10
10 # third party
11 # third party
11 import nose.tools as nt
12 import nose.tools as nt
@@ -33,3 +34,51 b' def test_protect_filename():'
33 for s1, s2 in pairs:
34 for s1, s2 in pairs:
34 s1p = completer.protect_filename(s1)
35 s1p = completer.protect_filename(s1)
35 nt.assert_equals(s1p, s2)
36 nt.assert_equals(s1p, s2)
37
38
39 def check_line_split(splitter, test_specs):
40 for part1, part2, split in test_specs:
41 cursor_pos = len(part1)
42 line = part1+part2
43 out = splitter.split_line(line, cursor_pos)
44 nt.assert_equal(out, split)
45
46
47 def test_line_split():
48 """Basice line splitter test with default specs."""
49 sp = completer.CompletionSplitter()
50 # The format of the test specs is: part1, part2, expected answer. Parts 1
51 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
52 # was at the end of part1. So an empty part2 represents someone hitting
53 # tab at the end of the line, the most common case.
54 t = [('run some/scrip', '', 'some/scrip'),
55 ('run scripts/er', 'ror.py foo', 'scripts/er'),
56 ('echo $HOM', '', 'HOM'),
57 ('print sys.pa', '', 'sys.pa'),
58 ('print(sys.pa', '', 'sys.pa'),
59 ("execfile('scripts/er", '', 'scripts/er'),
60 ('a[x.', '', 'x.'),
61 ('a[x.', 'y', 'x.'),
62 ('cd "some_file/', '', 'some_file/'),
63 ]
64 check_line_split(sp, t)
65
66
67 class CompletionSplitterTestCase(unittest.TestCase):
68 def setUp(self):
69 self.sp = completer.CompletionSplitter()
70
71 def test_delim_setting(self):
72 self.sp.delims = ' '
73 # Validate that property handling works ok
74 nt.assert_equal(self.sp.delims, ' ')
75 nt.assert_equal(self.sp.delim_expr, '[\ ]')
76
77 def test_spaces(self):
78 """Test with only spaces as split chars."""
79 self.sp.delims = ' '
80 t = [('foo', '', 'foo'),
81 ('run foo', '', 'foo'),
82 ('run foo', 'bar', 'foo'),
83 ]
84 check_line_split(self.sp, t)
General Comments 0
You need to be logged in to leave comments. Login now