Skip to content

Commit 49ce031

Browse files
committed
Add IPython completion hook
1 parent 9d1833f commit 49ce031

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

julia/magic.py

+69
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,74 @@ def julia(self, line, cell=None):
6666
return ans
6767

6868

69+
class JuliaCompleter(object):
70+
71+
"""
72+
Simple completion for ``%julia`` line magic.
73+
"""
74+
75+
@property
76+
def jlcomplete_texts(self):
77+
try:
78+
return self._jlcomplete_texts
79+
except AttributeError:
80+
pass
81+
82+
julia = Julia()
83+
if julia.eval('VERSION < v"0.7-"'):
84+
self._jlcomplete_texts = lambda *_: []
85+
return self._jlcomplete_texts
86+
87+
self._jlcomplete_texts = julia.eval("""
88+
import REPL
89+
(str, pos) -> begin
90+
ret, _, should_complete =
91+
REPL.completions(str, pos)
92+
if should_complete
93+
return map(REPL.completion_text, ret)
94+
else
95+
return []
96+
end
97+
end
98+
""")
99+
return self._jlcomplete_texts
100+
101+
def complete_command(self, _ip, event):
102+
pos = event.text_until_cursor.find("%julia")
103+
if pos < 0:
104+
return []
105+
pos += len("%julia") # pos: beginning of Julia code
106+
julia_code = event.line[pos:]
107+
julia_pos = len(event.text_until_cursor) - pos
108+
109+
completions = self.jlcomplete_texts(julia_code, julia_pos)
110+
if "." in event.symbol:
111+
# When completing (say) "Base.s" we need to add the prefix "Base."
112+
prefix = event.symbol.rsplit(".", 1)[0]
113+
completions = [".".join((prefix, c)) for c in completions]
114+
return completions
115+
# See:
116+
# IPython.core.completer.dispatch_custom_completer
117+
118+
@classmethod
119+
def register(cls, ip):
120+
"""
121+
Register `.complete_command` to IPython hook.
122+
123+
Parameters
124+
----------
125+
ip : IPython.InteractiveShell
126+
IPython `.InteractiveShell` instance passed to
127+
`load_ipython_extension`.
128+
"""
129+
ip.set_hook("complete_command", cls().complete_command,
130+
str_key="%julia")
131+
# See:
132+
# https://ipython.readthedocs.io/en/stable/api/generated/IPython.core.hooks.html
133+
# IPython.core.interactiveshell.init_completer
134+
# IPython.core.completerlib (quick_completer etc.)
135+
136+
69137
# Add to the global docstring the class information.
70138
__doc__ = __doc__.format(
71139
JULIAMAGICS_DOC=' ' * 8 + JuliaMagics.__doc__,
@@ -80,3 +148,4 @@ def julia(self, line, cell=None):
80148
def load_ipython_extension(ip):
81149
"""Load the extension in IPython."""
82150
ip.register_magics(JuliaMagics)
151+
JuliaCompleter.register(ip)

test/test_magic.py

+61
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
from IPython.testing.globalipapp import get_ipython
2+
import pytest
3+
4+
from julia.core import Julia
5+
from julia.magic import JuliaCompleter
26
import julia.magic
37

8+
try:
9+
from types import SimpleNamespace
10+
except ImportError:
11+
from argparse import Namespace as SimpleNamespace # Python 2
12+
13+
try:
14+
string_types = (unicode, str)
15+
except NameError:
16+
string_types = (str,)
17+
418

519
def get_julia_magics():
620
return julia.magic.JuliaMagics(shell=get_ipython())
@@ -32,3 +46,50 @@ def test_failure_cell():
3246
jm = get_julia_magics()
3347
ans = jm.julia(None, '1 += 1')
3448
assert ans is None
49+
50+
51+
def make_event(line, text_until_cursor=None, symbol=""):
52+
if text_until_cursor is None:
53+
text_until_cursor = line
54+
return SimpleNamespace(
55+
line=line,
56+
text_until_cursor=text_until_cursor,
57+
symbol=symbol,
58+
)
59+
60+
61+
completable_events = [
62+
make_event("%julia "),
63+
make_event("%julia si"),
64+
make_event("%julia Base.si"),
65+
]
66+
67+
uncompletable_events = [
68+
make_event(""),
69+
make_event("%julia si", text_until_cursor="%ju"),
70+
]
71+
72+
73+
def check_version():
74+
julia = Julia()
75+
if julia.eval('VERSION < v"0.7-"'):
76+
raise pytest.skip("Completion not supported in Julia 0.6")
77+
78+
79+
@pytest.mark.parametrize("event", completable_events)
80+
def test_completable_events(event):
81+
jc = JuliaCompleter()
82+
dummy_ipython = None
83+
completions = jc.complete_command(dummy_ipython, event)
84+
assert isinstance(completions, list)
85+
check_version()
86+
assert set(map(type, completions)) <= set(string_types)
87+
88+
89+
@pytest.mark.parametrize("event", uncompletable_events)
90+
def test_uncompletable_events(event):
91+
jc = JuliaCompleter()
92+
dummy_ipython = None
93+
completions = jc.complete_command(dummy_ipython, event)
94+
assert isinstance(completions, list)
95+
assert not completions

0 commit comments

Comments
 (0)