Show More
@@ -101,6 +101,27 b' else:' | |||||
101 | # Main functions and classes |
|
101 | # Main functions and classes | |
102 | #----------------------------------------------------------------------------- |
|
102 | #----------------------------------------------------------------------------- | |
103 |
|
103 | |||
|
104 | def has_open_quotes(s): | |||
|
105 | """Return whether a string has open quotes. | |||
|
106 | ||||
|
107 | This simply counts whether the number of quote characters of either type in | |||
|
108 | the string is odd. | |||
|
109 | ||||
|
110 | Returns | |||
|
111 | ------- | |||
|
112 | If there is an open quote, the quote character is returned. Else, return | |||
|
113 | False. | |||
|
114 | """ | |||
|
115 | # We check " first, then ', so complex cases with nested quotes will get | |||
|
116 | # the " to take precedence. | |||
|
117 | if s.count('"') % 2: | |||
|
118 | return '"' | |||
|
119 | elif s.count("'") % 2: | |||
|
120 | return "'" | |||
|
121 | else: | |||
|
122 | return False | |||
|
123 | ||||
|
124 | ||||
104 | def protect_filename(s): |
|
125 | def protect_filename(s): | |
105 | """Escape a string to protect certain characters.""" |
|
126 | """Escape a string to protect certain characters.""" | |
106 |
|
127 | |||
@@ -487,18 +508,19 b' class IPCompleter(Completer):' | |||||
487 | text_prefix = '' |
|
508 | text_prefix = '' | |
488 |
|
509 | |||
489 | text_until_cursor = self.text_until_cursor |
|
510 | text_until_cursor = self.text_until_cursor | |
490 |
|
|
511 | # track strings with open quotes | |
|
512 | open_quotes = has_open_quotes(text_until_cursor) | |||
|
513 | ||||
|
514 | if '(' in text_until_cursor or '[' in text_until_cursor: | |||
|
515 | lsplit = text | |||
|
516 | else: | |||
491 | try: |
|
517 | try: | |
492 | # arg_split ~ shlex.split, but with unicode bugs fixed by us |
|
518 | # arg_split ~ shlex.split, but with unicode bugs fixed by us | |
493 | lsplit = arg_split(text_until_cursor)[-1] |
|
519 | lsplit = arg_split(text_until_cursor)[-1] | |
494 | except ValueError: |
|
520 | except ValueError: | |
495 | # typically an unmatched ", or backslash without escaped char. |
|
521 | # typically an unmatched ", or backslash without escaped char. | |
496 | if text_until_cursor.count('"')==1: |
|
522 | if open_quotes: | |
497 | open_quotes = 1 |
|
523 | lsplit = text_until_cursor.split(open_quotes)[-1] | |
498 | lsplit = text_until_cursor.split('"')[-1] |
|
|||
499 | elif text_until_cursor.count("'")==1: |
|
|||
500 | open_quotes = 1 |
|
|||
501 | lsplit = text_until_cursor.split("'")[-1] |
|
|||
502 | else: |
|
524 | else: | |
503 | return [] |
|
525 | return [] | |
504 | except IndexError: |
|
526 | except IndexError: | |
@@ -506,18 +528,19 b' class IPCompleter(Completer):' | |||||
506 | lsplit = "" |
|
528 | lsplit = "" | |
507 |
|
529 | |||
508 | if not open_quotes and lsplit != protect_filename(lsplit): |
|
530 | if not open_quotes and lsplit != protect_filename(lsplit): | |
509 | # if protectables are found, do matching on the whole escaped |
|
531 | # if protectables are found, do matching on the whole escaped name | |
510 | # name |
|
532 | has_protectables = True | |
511 | has_protectables = 1 |
|
|||
512 | text0,text = text,lsplit |
|
533 | text0,text = text,lsplit | |
513 | else: |
|
534 | else: | |
514 |
has_protectables = |
|
535 | has_protectables = False | |
515 | text = os.path.expanduser(text) |
|
536 | text = os.path.expanduser(text) | |
516 |
|
537 | |||
517 | if text == "": |
|
538 | if text == "": | |
518 | return [text_prefix + protect_filename(f) for f in self.glob("*")] |
|
539 | return [text_prefix + protect_filename(f) for f in self.glob("*")] | |
519 |
|
540 | |||
|
541 | # Compute the matches from the filesystem | |||
520 | m0 = self.clean_glob(text.replace('\\','')) |
|
542 | m0 = self.clean_glob(text.replace('\\','')) | |
|
543 | ||||
521 | if has_protectables: |
|
544 | if has_protectables: | |
522 | # If we had protectables, we need to revert our changes to the |
|
545 | # If we had protectables, we need to revert our changes to the | |
523 | # beginning of filename so that we don't double-write the part |
|
546 | # beginning of filename so that we don't double-write the part | |
@@ -711,7 +734,7 b' class IPCompleter(Completer):' | |||||
711 | return None |
|
734 | return None | |
712 |
|
735 | |||
713 | def complete(self, text=None, line_buffer=None, cursor_pos=None): |
|
736 | def complete(self, text=None, line_buffer=None, cursor_pos=None): | |
714 | """Return the state-th possible completion for 'text'. |
|
737 | """Find completions for the given text and line context. | |
715 |
|
738 | |||
716 | This is called successively with state == 0, 1, 2, ... until it |
|
739 | This is called successively with state == 0, 1, 2, ... until it | |
717 | returns None. The completion should begin with 'text'. |
|
740 | returns None. The completion should begin with 'text'. | |
@@ -734,6 +757,14 b' class IPCompleter(Completer):' | |||||
734 | cursor_pos : int, optional |
|
757 | cursor_pos : int, optional | |
735 | Index of the cursor in the full line buffer. Should be provided by |
|
758 | Index of the cursor in the full line buffer. Should be provided by | |
736 | remote frontends where kernel has no access to frontend state. |
|
759 | remote frontends where kernel has no access to frontend state. | |
|
760 | ||||
|
761 | Returns | |||
|
762 | ------- | |||
|
763 | text : str | |||
|
764 | Text that was actually used in the completion. | |||
|
765 | ||||
|
766 | matches : list | |||
|
767 | A list of completion matches. | |||
737 | """ |
|
768 | """ | |
738 | #io.rprint('\nCOMP1 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg |
|
769 | #io.rprint('\nCOMP1 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg | |
739 |
|
770 | |||
@@ -772,7 +803,7 b' class IPCompleter(Completer):' | |||||
772 | except: |
|
803 | except: | |
773 | # Show the ugly traceback if the matcher causes an |
|
804 | # Show the ugly traceback if the matcher causes an | |
774 | # exception, but do NOT crash the kernel! |
|
805 | # exception, but do NOT crash the kernel! | |
775 | sys.excepthook() |
|
806 | sys.excepthook(*sys.exc_info()) | |
776 | else: |
|
807 | else: | |
777 | for matcher in self.matchers: |
|
808 | for matcher in self.matchers: | |
778 | self.matches = matcher(text) |
|
809 | self.matches = matcher(text) |
@@ -5,6 +5,7 b'' | |||||
5 | #----------------------------------------------------------------------------- |
|
5 | #----------------------------------------------------------------------------- | |
6 |
|
6 | |||
7 | # stdlib |
|
7 | # stdlib | |
|
8 | import os | |||
8 | import sys |
|
9 | import sys | |
9 | import unittest |
|
10 | import unittest | |
10 |
|
11 | |||
@@ -13,6 +14,7 b' import nose.tools as nt' | |||||
13 |
|
14 | |||
14 | # our own packages |
|
15 | # our own packages | |
15 | from IPython.core import completer |
|
16 | from IPython.core import completer | |
|
17 | from IPython.utils.tempdir import TemporaryDirectory | |||
16 |
|
18 | |||
17 | #----------------------------------------------------------------------------- |
|
19 | #----------------------------------------------------------------------------- | |
18 | # Test functions |
|
20 | # Test functions | |
@@ -113,3 +115,44 b' class CompletionSplitterTestCase(unittest.TestCase):' | |||||
113 | ('run foo', 'bar', 'foo'), |
|
115 | ('run foo', 'bar', 'foo'), | |
114 | ] |
|
116 | ] | |
115 | check_line_split(self.sp, t) |
|
117 | check_line_split(self.sp, t) | |
|
118 | ||||
|
119 | ||||
|
120 | def test_has_open_quotes1(): | |||
|
121 | for s in ["'", "'''", "'hi' '"]: | |||
|
122 | nt.assert_equal(completer.has_open_quotes(s), "'") | |||
|
123 | ||||
|
124 | ||||
|
125 | def test_has_open_quotes2(): | |||
|
126 | for s in ['"', '"""', '"hi" "']: | |||
|
127 | nt.assert_equal(completer.has_open_quotes(s), '"') | |||
|
128 | ||||
|
129 | ||||
|
130 | def test_has_open_quotes3(): | |||
|
131 | for s in ["''", "''' '''", "'hi' 'ipython'"]: | |||
|
132 | nt.assert_false(completer.has_open_quotes(s)) | |||
|
133 | ||||
|
134 | ||||
|
135 | def test_has_open_quotes4(): | |||
|
136 | for s in ['""', '""" """', '"hi" "ipython"']: | |||
|
137 | nt.assert_false(completer.has_open_quotes(s)) | |||
|
138 | ||||
|
139 | ||||
|
140 | def test_file_completions(): | |||
|
141 | ||||
|
142 | ip = get_ipython() | |||
|
143 | with TemporaryDirectory() as tmpdir: | |||
|
144 | prefix = os.path.join(tmpdir, 'foo') | |||
|
145 | suffixes = map(str, [1,2]) | |||
|
146 | names = [prefix+s for s in suffixes] | |||
|
147 | for n in names: | |||
|
148 | open(n, 'w').close() | |||
|
149 | ||||
|
150 | # Check simple completion | |||
|
151 | c = ip.complete(prefix)[1] | |||
|
152 | nt.assert_equal(c, names) | |||
|
153 | ||||
|
154 | # Now check with a function call | |||
|
155 | cmd = 'a = f("%s' % prefix | |||
|
156 | c = ip.complete(prefix, cmd)[1] | |||
|
157 | comp = [prefix+s for s in suffixes] | |||
|
158 | nt.assert_equal(c, comp) |
General Comments 0
You need to be logged in to leave comments.
Login now