##// 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 # 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
@@ -485,39 +506,41 b' class IPCompleter(Completer):'
485 text_prefix = '!'
506 text_prefix = '!'
486 else:
507 else:
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 open_quotes = 0 # track strings with open quotes
511 # track strings with open quotes
491 try:
512 open_quotes = has_open_quotes(text_until_cursor)
492 # arg_split ~ shlex.split, but with unicode bugs fixed by us
513
493 lsplit = arg_split(text_until_cursor)[-1]
514 if '(' in text_until_cursor or '[' in text_until_cursor:
494 except ValueError:
515 lsplit = text
495 # typically an unmatched ", or backslash without escaped char.
516 else:
496 if text_until_cursor.count('"')==1:
517 try:
497 open_quotes = 1
518 # arg_split ~ shlex.split, but with unicode bugs fixed by us
498 lsplit = text_until_cursor.split('"')[-1]
519 lsplit = arg_split(text_until_cursor)[-1]
499 elif text_until_cursor.count("'")==1:
520 except ValueError:
500 open_quotes = 1
521 # typically an unmatched ", or backslash without escaped char.
501 lsplit = text_until_cursor.split("'")[-1]
522 if open_quotes:
502 else:
523 lsplit = text_until_cursor.split(open_quotes)[-1]
503 return []
524 else:
504 except IndexError:
525 return []
505 # tab pressed on empty line
526 except IndexError:
506 lsplit = ""
527 # tab pressed on empty line
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 = 0
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