##// END OF EJS Templates
Fix bug in tab completion of filenames when quotes are present....
Fernando Perez -
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
@@ -485,39 +506,41 b' class IPCompleter(Completer):'
485 506 text_prefix = '!'
486 507 else:
487 508 text_prefix = ''
488
509
489 510 text_until_cursor = self.text_until_cursor
490 open_quotes = 0 # track strings with open quotes
491 try:
492 # arg_split ~ shlex.split, but with unicode bugs fixed by us
493 lsplit = arg_split(text_until_cursor)[-1]
494 except ValueError:
495 # 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]
502 else:
503 return []
504 except IndexError:
505 # tab pressed on empty line
506 lsplit = ""
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:
517 try:
518 # arg_split ~ shlex.split, but with unicode bugs fixed by us
519 lsplit = arg_split(text_until_cursor)[-1]
520 except ValueError:
521 # typically an unmatched ", or backslash without escaped char.
522 if open_quotes:
523 lsplit = text_until_cursor.split(open_quotes)[-1]
524 else:
525 return []
526 except IndexError:
527 # tab pressed on empty line
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 = 0
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