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 |
# |
|
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 |
|
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