Repository: ipython/ipython

Issue:
Filename completion when run script with %run
When I type `~/` and press `Tab`, it completes with files in my home directory. Works also when a script runs `input`.

_But_ when I want to run a script with args, the tab completion only works with directories, not files.

Ex:
`%run my_script ~`
press Tab will expend: `/home/me/dir1 /home/me/dir2`
but not: `/home/me/dir1 /home/me/dir2 /home/me/file`

Is it possible tu have the file completion just like in a shell?



Discussion:
That's odd. I see it too. It can complete files in the working directory, but not if you have to give any path components.

might be a delimiter issue, then.

Ok, but in which part of the code then?

Completion is in `IPython.core.completer` and `IPython.core.completerlib`.


Resolving patch:
diff --git a/IPython/core/completerlib.py b/IPython/core/completerlib.py
--- a/IPython/core/completerlib.py
+++ b/IPython/core/completerlib.py
@@ -253,7 +253,11 @@ def magic_run_completer(self, event):
     """Complete files that end in .py or .ipy or .ipynb for the %run command.
     """
     comps = arg_split(event.line, strict=False)
-    relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
+    # relpath should be the current token that we need to complete.
+    if (len(comps) > 1) and (not event.line.endswith(' ')):
+        relpath = comps[-1].strip("'\"")
+    else:
+        relpath = ''
 
     #print("\nev=", event)  # dbg
     #print("rp=", relpath)  # dbg
@@ -263,20 +267,23 @@ def magic_run_completer(self, event):
     isdir = os.path.isdir
     relpath, tilde_expand, tilde_val = expand_user(relpath)
 
-    dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
-
     # Find if the user has already typed the first filename, after which we
     # should complete on all files, since after the first one other files may
     # be arguments to the input script.
 
     if any(magic_run_re.match(c) for c in comps):
-        pys =  [f.replace('\\','/') for f in lglob('*')]
+        matches =  [f.replace('\\','/') + ('/' if isdir(f) else '')
+                            for f in lglob(relpath+'*')]
     else:
+        dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
         pys =  [f.replace('\\','/')
                 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
                 lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')]
+
+        matches = dirs + pys
+
     #print('run comp:', dirs+pys) # dbg
-    return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
+    return [compress_user(p, tilde_expand, tilde_val) for p in matches]
 
 
 def cd_completer(self, event):
diff --git a/IPython/nbconvert/utils/lexers.py b/IPython/nbconvert/utils/lexers.py
--- a/IPython/nbconvert/utils/lexers.py
+++ b/IPython/nbconvert/utils/lexers.py
@@ -4,12 +4,10 @@
 
 This includes:
 
-    IPythonLexer
-    IPython3Lexer
+    IPythonLexer, IPython3Lexer
         Lexers for pure IPython (python + magic/shell commands)
 
-    IPythonPartialTracebackLexer
-    IPythonTracebackLexer
+    IPythonPartialTracebackLexer, IPythonTracebackLexer
         Supports 2.x and 3.x via keyword `python3`.  The partial traceback
         lexer reads everything but the Python code appearing in a traceback.
         The full lexer combines the partial lexer with an IPython lexer.
@@ -209,6 +207,7 @@ class IPythonConsoleLexer(Lexer):
         .. code-block:: ipythonconsole
 
             In [1]: raise Exception
+
             ---------------------------------------------------------------------------
             Exception                                 Traceback (most recent call last)
             <ipython-input-1-fca2ab0ca76b> in <module>()


Test patch:
diff --git a/IPython/core/tests/test_completerlib.py b/IPython/core/tests/test_completerlib.py
--- a/IPython/core/tests/test_completerlib.py
+++ b/IPython/core/tests/test_completerlib.py
@@ -15,6 +15,8 @@
 import unittest
 from os.path import join
 
+import nose.tools as nt
+
 from IPython.core.completerlib import magic_run_completer, module_completion
 from IPython.utils import py3compat
 from IPython.utils.tempdir import TemporaryDirectory
@@ -29,11 +31,17 @@ def __init__(self, line):
 # Test functions begin
 #-----------------------------------------------------------------------------
 class Test_magic_run_completer(unittest.TestCase):
+    files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
+    dirs = [u"adir/", "bdir/"]
+
     def setUp(self):
         self.BASETESTDIR = tempfile.mkdtemp()
-        for fil in [u"aao.py", u"a.py", u"b.py"]:
+        for fil in self.files:
             with open(join(self.BASETESTDIR, fil), "w") as sfile:
                 sfile.write("pass\n")
+        for d in self.dirs:
+            os.mkdir(join(self.BASETESTDIR, d))
+
         self.oldpath = py3compat.getcwd()
         os.chdir(self.BASETESTDIR)
 
@@ -47,7 +55,7 @@ def test_1(self):
         event = MockEvent(u"%run a")
         mockself = None
         match = set(magic_run_completer(mockself, event))
-        self.assertEqual(match, set([u"a.py", u"aao.py"]))
+        self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
 
     def test_2(self):
         """Test magic_run_completer, should match one alterntive
@@ -62,23 +70,23 @@ def test_3(self):
         event = MockEvent(u'%run "a')
         mockself = None
         match = set(magic_run_completer(mockself, event))
-        self.assertEqual(match, set([u"a.py", u"aao.py"]))
-
-    def test_import_invalid_module(self):
-        """Testing of issue https://github.com/ipython/ipython/issues/1107"""
-        invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
-        valid_module_names = set(['foobar'])
-        with TemporaryDirectory() as tmpdir:
-            sys.path.insert( 0, tmpdir )
-            for name in invalid_module_names | valid_module_names:
-                filename = os.path.join(tmpdir, name + '.py')
-                open(filename, 'w').close()
-
-            s = set( module_completion('import foo') )
-            intersection = s.intersection(invalid_module_names)
-            self.assertFalse(intersection, intersection)
-            
-            assert valid_module_names.issubset(s), valid_module_names.intersection(s)
+        self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
+
+    def test_completion_more_args(self):
+        event = MockEvent(u'%run a.py ')
+        match = set(magic_run_completer(None, event))
+        self.assertEqual(match, set(self.files + self.dirs))
+
+    def test_completion_in_dir(self):
+        # Github issue #3459
+        event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
+        print(repr(event.line))
+        match = set(magic_run_completer(None, event))
+        # We specifically use replace here rather than normpath, because
+        # at one point there were duplicates 'adir' and 'adir/', and normpath
+        # would hide the failure for that.
+        self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
+                            for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
 
 class Test_magic_run_completer_nonascii(unittest.TestCase):
     @onlyif_unicode_paths
@@ -119,3 +127,21 @@ def test_3(self):
         mockself = None
         match = set(magic_run_completer(mockself, event))
         self.assertEqual(match, set([u"a.py", u"aaø.py"]))
+
+# module_completer:
+
+def test_import_invalid_module():
+    """Testing of issue https://github.com/ipython/ipython/issues/1107"""
+    invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
+    valid_module_names = set(['foobar'])
+    with TemporaryDirectory() as tmpdir:
+        sys.path.insert( 0, tmpdir )
+        for name in invalid_module_names | valid_module_names:
+            filename = os.path.join(tmpdir, name + '.py')
+            open(filename, 'w').close()
+
+        s = set( module_completion('import foo') )
+        intersection = s.intersection(invalid_module_names)
+        nt.assert_equal(intersection, set())
+
+        assert valid_module_names.issubset(s), valid_module_names.intersection(s)