From 3e7dca2888b33a4d4df768a71b0ea485272f5df8 2010-09-06 19:12:46 From: Fernando Perez Date: 2010-09-06 19:12:46 Subject: [PATCH] Fix bugs with completions of paths that have ~ in them. Now %run and %cd completers should behave much better in the presence of ~ in the input string. --- diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 474ce86..73c74f7 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -119,6 +119,52 @@ def mark_dirs(matches): return out +def expand_user(path): + """Expand '~'-style usernames in strings. + + This is similar to :func:`os.path.expanduser`, but it computes and returns + extra information that will be useful if the input was being used in + computing completions, and you wish to return the completions with the + original '~' instead of its expanded value. + + Parameters + ---------- + path : str + String to be expanded. If no ~ is present, the output is the same as the + input. + + Returns + ------- + newpath : str + Result of ~ expansion in the input path. + tilde_expand : bool + Whether any expansion was performed or not. + tilde_val : str + The value that ~ was replaced with. + """ + # Default values + tilde_expand = False + tilde_val = '' + newpath = path + + if path.startswith('~'): + tilde_expand = True + rest = path[1:] + newpath = os.path.expanduser(path) + tilde_val = newpath.replace(rest, '') + + return newpath, tilde_expand, tilde_val + + +def compress_user(path, tilde_expand, tilde_val): + """Does the opposite of expand_user, with its outputs. + """ + if tilde_expand: + return path.replace(tilde_val, '~') + else: + return path + + def single_dir_expand(matches): "Recursively expand match lists containing a single dir." diff --git a/IPython/core/completerlib.py b/IPython/core/completerlib.py index bf0abad..23ad50c 100644 --- a/IPython/core/completerlib.py +++ b/IPython/core/completerlib.py @@ -28,6 +28,7 @@ from time import time from zipimport import zipimporter # Our own imports +from IPython.core.completer import expand_user, compress_user from IPython.core.error import TryNext # FIXME: this should be pulled in with the right call via the component system @@ -64,7 +65,11 @@ def shlex_split(x): # # Example: # %run "c:/python -> ['%run','"c:/python'] - + + # shlex.split has unicode bugs, so encode first to str + if isinstance(x, unicode): + x = x.encode(sys.stdin.encoding) + endofline = [] while x != '': try: @@ -253,6 +258,8 @@ def module_completer(self,event): return module_completion(event.line) +# FIXME: there's a lot of logic common to the run, cd and builtin file +# completers, that is currently reimplemented in each. def magic_run_completer(self, event): """Complete files that end in .py or .ipy for the %run command. @@ -260,14 +267,14 @@ def magic_run_completer(self, event): comps = shlex_split(event.line) relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"") - #print "\nev=",event # dbg - #print "rp=",relpath # dbg - #print 'comps=',comps # dbg + #print("\nev=", event) # dbg + #print("rp=", relpath) # dbg + #print('comps=', comps) # dbg lglob = glob.glob isdir = os.path.isdir - if relpath.startswith('~'): - relpath = os.path.expanduser(relpath) + 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 @@ -280,7 +287,8 @@ def magic_run_completer(self, event): pys = [f.replace('\\','/') for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + lglob(relpath + '*.pyw')] - return dirs + pys + #print('run comp:', dirs+pys) # dbg + return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys] def cd_completer(self, event): @@ -308,9 +316,10 @@ def cd_completer(self, event): if event.symbol.startswith('--'): return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']] - - if relpath.startswith('~'): - relpath = os.path.expanduser(relpath).replace('\\','/') + + # Expand ~ in path and normalize directory separators. + relpath, tilde_expand, tilde_val = expand_user(relpath) + relpath = relpath.replace('\\','/') found = [] for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') @@ -320,11 +329,11 @@ def cd_completer(self, event): # for this is elsewhere raise TryNext - found.append( d ) + found.append(d) if not found: if os.path.isdir(relpath): - return [relpath] + return [compress_user(relpath, tilde_expand, tilde_val)] # if no completions so far, try bookmarks bks = self.db.get('bookmarks',{}).keys() @@ -334,4 +343,4 @@ def cd_completer(self, event): raise TryNext - return found + return [compress_user(p, tilde_expand, tilde_val) for p in found]