Show More
@@ -61,6 +61,7 b' used, and this module (and the readline module) are silently inactive.' | |||
|
61 | 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 | 67 | # Imports |
@@ -79,7 +80,7 b' import sys' | |||
|
79 | 80 | |
|
80 | 81 | from IPython.core.error import TryNext |
|
81 | 82 | from IPython.core.prefilter import ESC_MAGIC |
|
82 | from IPython.utils import generics | |
|
83 | from IPython.utils import generics, io | |
|
83 | 84 | from IPython.utils.frame import debugx |
|
84 | 85 | from IPython.utils.dir2 import dir2 |
|
85 | 86 | |
@@ -138,9 +139,60 b' def single_dir_expand(matches):' | |||
|
138 | 139 | else: |
|
139 | 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 | 196 | def __init__(self,namespace=None,global_namespace=None): |
|
145 | 197 | """Create a new completer for the command line. |
|
146 | 198 | |
@@ -631,7 +683,8 b' class IPCompleter(Completer):' | |||
|
631 | 683 | Index of the cursor in the full line buffer. Should be provided by |
|
632 | 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 | 688 | magic_escape = self.magic_escape |
|
636 | 689 | self.full_lbuf = line_buffer |
|
637 | 690 | self.lbuf = self.full_lbuf[:cursor_pos] |
@@ -663,7 +716,7 b' class IPCompleter(Completer):' | |||
|
663 | 716 | # simply collapse the dict into a list for readline, but we'd have |
|
664 | 717 | # richer completion semantics in other evironments. |
|
665 | 718 | self.matches = sorted(set(self.matches)) |
|
666 |
# |
|
|
719 | #io.rprint('MATCHES', self.matches) # dbg | |
|
667 | 720 | return self.matches |
|
668 | 721 | |
|
669 | 722 | def rlcomplete(self, text, state): |
@@ -679,15 +732,15 b' class IPCompleter(Completer):' | |||
|
679 | 732 | |
|
680 | 733 | state : int |
|
681 | 734 | Counter used by readline. |
|
682 | ||
|
683 | 735 | """ |
|
684 | ||
|
685 | #print "rlcomplete! '%s' %s" % (text, state) # dbg | |
|
686 | ||
|
687 | 736 | if state==0: |
|
737 | ||
|
688 | 738 | self.full_lbuf = line_buffer = self.get_line_buffer() |
|
689 | 739 | cursor_pos = self.get_endidx() |
|
690 | 740 | |
|
741 | #io.rprint("\nRLCOMPLETE: %r %r %r" % | |
|
742 | # (text, line_buffer, cursor_pos) ) # dbg | |
|
743 | ||
|
691 | 744 | # if there is only a tab on a line with only whitespace, instead of |
|
692 | 745 | # the mostly useless 'do you want to see all million completions' |
|
693 | 746 | # message, just do the right thing and give the user his tab! |
@@ -699,7 +752,7 b' class IPCompleter(Completer):' | |||
|
699 | 752 | |
|
700 | 753 | # don't apply this on 'dumb' terminals, such as emacs buffers, so |
|
701 | 754 | # we don't interfere with their own tab-completion mechanism. |
|
702 |
if not (self.dumb_terminal or |
|
|
755 | if not (self.dumb_terminal or line_buffer.strip()): | |
|
703 | 756 | self.readline.insert_text('\t') |
|
704 | 757 | sys.stdout.flush() |
|
705 | 758 | return None |
@@ -719,4 +772,3 b' class IPCompleter(Completer):' | |||
|
719 | 772 | return self.matches[state] |
|
720 | 773 | except IndexError: |
|
721 | 774 | return None |
|
722 |
@@ -6,6 +6,7 b'' | |||
|
6 | 6 | |
|
7 | 7 | # stdlib |
|
8 | 8 | import sys |
|
9 | import unittest | |
|
9 | 10 | |
|
10 | 11 | # third party |
|
11 | 12 | import nose.tools as nt |
@@ -33,3 +34,51 b' def test_protect_filename():' | |||
|
33 | 34 | for s1, s2 in pairs: |
|
34 | 35 | s1p = completer.protect_filename(s1) |
|
35 | 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