Show More
@@ -101,6 +101,27 b' else:' | |||
|
101 | 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 | 125 | def protect_filename(s): |
|
105 | 126 | """Escape a string to protect certain characters.""" |
|
106 | 127 | |
@@ -487,18 +508,19 b' class IPCompleter(Completer):' | |||
|
487 | 508 | text_prefix = '' |
|
488 | 509 | |
|
489 | 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 | 517 | try: |
|
492 | 518 | # arg_split ~ shlex.split, but with unicode bugs fixed by us |
|
493 | 519 | lsplit = arg_split(text_until_cursor)[-1] |
|
494 | 520 | except ValueError: |
|
495 | 521 | # typically an unmatched ", or backslash without escaped char. |
|
496 | if text_until_cursor.count('"')==1: | |
|
497 | 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] | |
|
522 | if open_quotes: | |
|
523 | lsplit = text_until_cursor.split(open_quotes)[-1] | |
|
502 | 524 | else: |
|
503 | 525 | return [] |
|
504 | 526 | except IndexError: |
@@ -506,18 +528,19 b' class IPCompleter(Completer):' | |||
|
506 | 528 | lsplit = "" |
|
507 | 529 | |
|
508 | 530 | if not open_quotes and lsplit != protect_filename(lsplit): |
|
509 | # if protectables are found, do matching on the whole escaped | |
|
510 | # name | |
|
511 | has_protectables = 1 | |
|
531 | # if protectables are found, do matching on the whole escaped name | |
|
532 | has_protectables = True | |
|
512 | 533 | text0,text = text,lsplit |
|
513 | 534 | else: |
|
514 |
has_protectables = |
|
|
535 | has_protectables = False | |
|
515 | 536 | text = os.path.expanduser(text) |
|
516 | 537 | |
|
517 | 538 | if text == "": |
|
518 | 539 | return [text_prefix + protect_filename(f) for f in self.glob("*")] |
|
519 | 540 | |
|
541 | # Compute the matches from the filesystem | |
|
520 | 542 | m0 = self.clean_glob(text.replace('\\','')) |
|
543 | ||
|
521 | 544 | if has_protectables: |
|
522 | 545 | # If we had protectables, we need to revert our changes to the |
|
523 | 546 | # beginning of filename so that we don't double-write the part |
@@ -711,7 +734,7 b' class IPCompleter(Completer):' | |||
|
711 | 734 | return None |
|
712 | 735 | |
|
713 | 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 | 739 | This is called successively with state == 0, 1, 2, ... until it |
|
717 | 740 | returns None. The completion should begin with 'text'. |
@@ -734,6 +757,14 b' class IPCompleter(Completer):' | |||
|
734 | 757 | cursor_pos : int, optional |
|
735 | 758 | Index of the cursor in the full line buffer. Should be provided by |
|
736 | 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 | 769 | #io.rprint('\nCOMP1 %r %r %r' % (text, line_buffer, cursor_pos)) # dbg |
|
739 | 770 | |
@@ -772,7 +803,7 b' class IPCompleter(Completer):' | |||
|
772 | 803 | except: |
|
773 | 804 | # Show the ugly traceback if the matcher causes an |
|
774 | 805 | # exception, but do NOT crash the kernel! |
|
775 | sys.excepthook() | |
|
806 | sys.excepthook(*sys.exc_info()) | |
|
776 | 807 | else: |
|
777 | 808 | for matcher in self.matchers: |
|
778 | 809 | self.matches = matcher(text) |
@@ -5,6 +5,7 b'' | |||
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | |
|
7 | 7 | # stdlib |
|
8 | import os | |
|
8 | 9 | import sys |
|
9 | 10 | import unittest |
|
10 | 11 | |
@@ -13,6 +14,7 b' import nose.tools as nt' | |||
|
13 | 14 | |
|
14 | 15 | # our own packages |
|
15 | 16 | from IPython.core import completer |
|
17 | from IPython.utils.tempdir import TemporaryDirectory | |
|
16 | 18 | |
|
17 | 19 | #----------------------------------------------------------------------------- |
|
18 | 20 | # Test functions |
@@ -113,3 +115,44 b' class CompletionSplitterTestCase(unittest.TestCase):' | |||
|
113 | 115 | ('run foo', 'bar', 'foo'), |
|
114 | 116 | ] |
|
115 | 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