diff --git a/IPython/core/completer.py b/IPython/core/completer.py index ee07b26..663d858 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -177,6 +177,45 @@ def compress_user(path, tilde_expand, tilde_val): return path + +def penalize_magics_key(word): + """key for sorting that penalizes magic commands in the ordering + + Normal words are left alone. + + Magic commands have the initial % moved to the end, e.g. + %matplotlib is transformed as follows: + + %matplotlib -> matplotlib% + + [The choice of the final % is arbitrary.] + + Since "matplotlib" < "matplotlib%" as strings, + "timeit" will appear before the magic "%timeit" in the ordering + + For consistency, move "%%" to the end, so cell magics appear *after* + line magics with the same name. + + A check is performed that there are no other "%" in the string; + if there are, then the string is not a magic command and is left unchanged. + + """ + + # Move any % signs from start to end of the key + # provided there are no others elsewhere in the string + + if word[:2] == "%%": + if not "%" in word[2:]: + return word[2:] + "%%" + + if word[:1] == "%": + if not "%" in word[1:]: + return word[1:] + "%" + + return word + + + class Bunch(object): pass @@ -888,7 +927,10 @@ class IPCompleter(Completer): # different types of objects. The rlcomplete() method could then # simply collapse the dict into a list for readline, but we'd have # richer completion semantics in other evironments. - self.matches = sorted(set(self.matches)) + + # use penalize_magics_key to put magics after variables with same name + self.matches = sorted(set(self.matches), key=penalize_magics_key) + #io.rprint('COMP TEXT, MATCHES: %r, %r' % (text, self.matches)) # dbg return text, self.matches diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index d663bde..0ad52e8 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -362,3 +362,31 @@ def test_line_cell_magics(): nt.assert_not_in('%_bar_cellm', matches) nt.assert_in('%%_bar_cellm', matches) + +def test_magic_completion_order(): + + ip = get_ipython() + c = ip.Completer + + # Test ordering of magics and non-magics with the same name + # We want the non-magic first + + # Before importing matplotlib, there should only be one option: + + text, matches = c.complete('mat') + nt.assert_equal(matches, ["%matplotlib"]) + + + ip.run_cell("matplotlib = 1") # introduce name into namespace + + # After the import, there should be two options, ordered like this: + text, matches = c.complete('mat') + nt.assert_equal(matches, ["matplotlib", "%matplotlib"]) + + + ip.run_cell("timeit = 1") # define a user variable called 'timeit' + + # Order of user variable and line and cell magics with same name: + text, matches = c.complete('timeit') + nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"]) +