Skip to content

Add IPython completion hook in julia.magic #193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions julia/magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,74 @@ def julia(self, line, cell=None):
return ans


class JuliaCompleter(object):

"""
Simple completion for ``%julia`` line magic.
"""

@property
def jlcomplete_texts(self):
try:
return self._jlcomplete_texts
except AttributeError:
pass

julia = Julia()
if julia.eval('VERSION < v"0.7-"'):
self._jlcomplete_texts = lambda *_: []
return self._jlcomplete_texts

self._jlcomplete_texts = julia.eval("""
import REPL
(str, pos) -> begin
ret, _, should_complete =
REPL.completions(str, pos)
if should_complete
return map(REPL.completion_text, ret)
else
return []
end
end
""")
return self._jlcomplete_texts

def complete_command(self, _ip, event):
pos = event.text_until_cursor.find("%julia")
if pos < 0:
return []
pos += len("%julia") # pos: beginning of Julia code
julia_code = event.line[pos:]
julia_pos = len(event.text_until_cursor) - pos

completions = self.jlcomplete_texts(julia_code, julia_pos)
if "." in event.symbol:
# When completing (say) "Base.s" we need to add the prefix "Base."
prefix = event.symbol.rsplit(".", 1)[0]
completions = [".".join((prefix, c)) for c in completions]
return completions
# See:
# IPython.core.completer.dispatch_custom_completer

@classmethod
def register(cls, ip):
"""
Register `.complete_command` to IPython hook.

Parameters
----------
ip : IPython.InteractiveShell
IPython `.InteractiveShell` instance passed to
`load_ipython_extension`.
"""
ip.set_hook("complete_command", cls().complete_command,
str_key="%julia")
# See:
# https://ipython.readthedocs.io/en/stable/api/generated/IPython.core.hooks.html
# IPython.core.interactiveshell.init_completer
# IPython.core.completerlib (quick_completer etc.)


# Add to the global docstring the class information.
__doc__ = __doc__.format(
JULIAMAGICS_DOC=' ' * 8 + JuliaMagics.__doc__,
Expand All @@ -80,3 +148,4 @@ def julia(self, line, cell=None):
def load_ipython_extension(ip):
"""Load the extension in IPython."""
ip.register_magics(JuliaMagics)
JuliaCompleter.register(ip)
61 changes: 61 additions & 0 deletions test/test_magic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
from IPython.testing.globalipapp import get_ipython
import pytest

from julia.core import Julia
from julia.magic import JuliaCompleter
import julia.magic

try:
from types import SimpleNamespace
except ImportError:
from argparse import Namespace as SimpleNamespace # Python 2

try:
string_types = (unicode, str)
except NameError:
string_types = (str,)


def get_julia_magics():
return julia.magic.JuliaMagics(shell=get_ipython())
Expand Down Expand Up @@ -32,3 +46,50 @@ def test_failure_cell():
jm = get_julia_magics()
ans = jm.julia(None, '1 += 1')
assert ans is None


def make_event(line, text_until_cursor=None, symbol=""):
if text_until_cursor is None:
text_until_cursor = line
return SimpleNamespace(
line=line,
text_until_cursor=text_until_cursor,
symbol=symbol,
)


completable_events = [
make_event("%julia "),
make_event("%julia si"),
make_event("%julia Base.si"),
]

uncompletable_events = [
make_event(""),
make_event("%julia si", text_until_cursor="%ju"),
]


def check_version():
julia = Julia()
if julia.eval('VERSION < v"0.7-"'):
raise pytest.skip("Completion not supported in Julia 0.6")


@pytest.mark.parametrize("event", completable_events)
def test_completable_events(event):
jc = JuliaCompleter()
dummy_ipython = None
completions = jc.complete_command(dummy_ipython, event)
assert isinstance(completions, list)
check_version()
assert set(map(type, completions)) <= set(string_types)


@pytest.mark.parametrize("event", uncompletable_events)
def test_uncompletable_events(event):
jc = JuliaCompleter()
dummy_ipython = None
completions = jc.complete_command(dummy_ipython, event)
assert isinstance(completions, list)
assert not completions