diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 4280410..0365c1a 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -226,13 +226,13 @@ def has_open_quotes(s): return False -def protect_filename(s): +def protect_filename(s, protectables=PROTECTABLES): """Escape a string to protect certain characters.""" - if set(s) & set(PROTECTABLES): + if set(s) & set(protectables): if sys.platform == "win32": return '"' + s + '"' else: - return "".join(("\\" + c if c in PROTECTABLES else c) for c in s) + return "".join(("\\" + c if c in protectables else c) for c in s) else: return s @@ -1133,9 +1133,10 @@ class IPCompleter(Completer): else: if open_quotes: # if we have a string with an open quote, we don't need to - # protect the names at all (and we _shouldn't_, as it - # would cause bugs when the filesystem call is made). - matches = m0 + # protect the names beyond the quote (and we _shouldn't_, as + # it would cause bugs when the filesystem call is made). + matches = m0 if sys.platform == "win32" else\ + [protect_filename(f, open_quotes) for f in m0] else: matches = [text_prefix + protect_filename(f) for f in m0] diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 533ba42..e076e39 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -274,6 +274,37 @@ def test_local_file_completions(): nt.assert_true(comp.issubset(set(c))) +def test_quoted_file_completions(): + ip = get_ipython() + with TemporaryWorkingDirectory(): + name = "foo'bar" + open(name, 'w').close() + + # Don't escape Windows + escaped = name if sys.platform == "win32" else "foo\\'bar" + + # Single quote matches embedded single quote + text = "open('foo" + c = ip.Completer._complete(cursor_line=0, + cursor_pos=len(text), + full_text=text)[1] + nt.assert_equal(c, [escaped]) + + # Double quote requires no escape + text = 'open("foo' + c = ip.Completer._complete(cursor_line=0, + cursor_pos=len(text), + full_text=text)[1] + nt.assert_equal(c, [name]) + + # No quote requires an escape + text = '%ls foo' + c = ip.Completer._complete(cursor_line=0, + cursor_pos=len(text), + full_text=text)[1] + nt.assert_equal(c, [escaped]) + + def test_jedi(): """ A couple of issue we had with Jedi diff --git a/docs/source/whatsnew/pr/escape-quoted-filenames.rst b/docs/source/whatsnew/pr/escape-quoted-filenames.rst new file mode 100644 index 0000000..75d25a2 --- /dev/null +++ b/docs/source/whatsnew/pr/escape-quoted-filenames.rst @@ -0,0 +1,2 @@ +- Quotes in a filename are always escaped during tab-completion on non-Windows. + :ghpull:`10069`