Skip to content

Commit

Permalink
Merge pull request #503 from oscar-system/bl/improverepl
Browse files Browse the repository at this point in the history
  • Loading branch information
benlorenz authored Dec 24, 2024
2 parents c5ad5c0 + af2f22a commit a598c97
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 52 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Polymake"
uuid = "d720cf60-89b5-51f5-aff5-213f193123e7"
repo = "https://github.com/oscar-system/Polymake.jl.git"
version = "0.11.23"
version = "0.11.24"

[deps]
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
Expand Down
9 changes: 8 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
using Documenter, Polymake

makedocs(sitename = "Polymake.jl - Documentation", pages = ["index.md", "getting_started.md", "using_polymake_jl.md", "examples.md"])
makedocs(sitename = "Polymake.jl - Documentation",
pages = [
"index.md",
"getting_started.md",
"using_polymake_jl.md",
"examples.md",
"shell.md",
])

deploydocs(
repo = "github.com/oscar-system/Polymake.jl.git",
Expand Down
30 changes: 30 additions & 0 deletions docs/src/shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Polymake REPL

This section shows how to use the embedded polymake shell. For more details on the polymake shell please see the [polymake documentation](https://polymake.org/doku.php/user_guide/shell).

To access it type the dollar `$` symbol in an empty line or call `Polymake.prompt()`. The julia prompt should transition to `polymake (common) >`, indicating the currently active polymake application.

There are a few differences to the proper polymake shell:
- For technical reasons the default application is `common` instead of `polytope`. Thus most calls from other applications should be prefixed with the corresponding application, e.g. `$c = polytope::cube(3)`. The currently active application can also be changed with `application "someapplication";` (this will be indicated in the prompt).
- Incomplete input will be executed (and fail) immediately on pressing return. To enter multi-line input please use `Alt+Enter`.

## Passing data back and forth

Objects that are known to polymake can be assigned to and retrieved from the special module `Polymake.Shell`. The variable name in that module corresponds to a polymake shell variable of that name.

```julia
julia> c = polytope.cube(3);

julia> Polymake.Shell.cc = c;

polymake (common) > print $cc->F_VECTOR;
8 12 6

julia> Polymake.Shell.cc.H_VECTOR
pm::Vector<pm::Integer>
1 5 5 1
```

!!! warning
This feature is considered experimental!
There are very little checks on the data being passed around, so please avoid passing temporaries or incompatible objects.
5 changes: 1 addition & 4 deletions src/Polymake.jl
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,7 @@ function __init__()
set_julia_type("$(name)_OscarNumber", current_type)
end


if isdefined(Base, :active_repl)
run_polymake_repl()
end
try_init_polymake_repl()

if isdefined(Main, :IJulia) && Main.IJulia.inited
prepare_jupyter_kernel_for_visualization()
Expand Down
4 changes: 2 additions & 2 deletions src/perlobj.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ function give(obj::BigObject, prop::Union{Symbol,String})
internal_give(obj, prop)
end
catch ex
ex isa ErrorException && throw(PolymakeError(ex.msg))
ex isa ErrorException && rethrow(PolymakeError(ex.msg))
if (ex isa InterruptException)
@warn """Interrupting polymake is not safe.
SIGINT is disabled while waiting for polymake to finish its computations."""
end
rethrow(ex)
rethrow()
end
return convert_from_property_value(return_obj)
end
Expand Down
143 changes: 99 additions & 44 deletions src/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,28 @@ Base.getproperty(::ShellHelper, name::Symbol) = Polymake.call_function(:User, :g

Base.setproperty!(::ShellHelper, name::Symbol, value) = Polymake.call_function(:User, :set_shell_scalar, String(name), value)

const polymakerepl = Ref{LineEdit.Prompt}()

struct PolymakeCompletions <: LineEdit.CompletionProvider end

_color(str, magic_number=37) = Base.text_colors[(sum(Int, str) + magic_number) % 0xff]
_color(str, magic_number=69) = Base.text_colors[(sum(Int, str) + magic_number) % 0xff]
_get_app() = Polymake.get_current_app()
_get_prompt_prefix(str = _get_app()) = _color(str)
_get_prompt(app = _get_app()) = "polymake ($app) > "
_get_prompt_suffix() = Base.text_colors[:default]

function shell_execute_print(s::String, panel::LineEdit.Prompt)
res = convert(Tuple{Bool, String, String, String}, _shell_execute(s))
panel.prompt=Polymake.get_current_app()*" > "

panel.prompt_prefix=_color(panel.prompt)
app = _get_app()
panel.prompt=_get_prompt(app)
panel.prompt_prefix=_get_prompt_prefix(app)

if res[1]
print(Base.stdout, res[2])
# make sure there is a newline in between if both are nonempty
if !isempty(res[2]) && !isempty(res[3]) && last(res[2]) != '\n'
println()
end
print(Base.stderr, res[3])
if !isempty(res[4])
error(res[4])
Expand All @@ -35,36 +44,43 @@ function shell_execute_print(s::String, panel::LineEdit.Prompt)
if !isempty(res[4])
error(res[4])
else
error("polymake: incomplete statement")
error("polymake: incomplete statement, try Alt+Enter for multi-line input")
end
end
end

function LineEdit.complete_line(c::PolymakeCompletions, s)
partial = REPL.beforecursor(LineEdit.buffer(s))
full = LineEdit.input_string(s)
res = convert(Tuple{Int, Base.Array{String}}, shell_complete(full))
offset = first(res)
proposals = res[2]
return proposals, partial[end-offset+1:end], size(proposals,1) > 0
function LineEdit.complete_line(c::PolymakeCompletions, s; kwargs...)
try
partial = REPL.beforecursor(LineEdit.buffer(s))
full = LineEdit.input_string(s)
pmres = shell_complete(full)
res = convert(Tuple{Int, Base.Array{String}}, pmres)
offset = first(res)
proposals = res[2]
return proposals, partial[end-offset+1:end], size(proposals,1) > 0
catch
@debug "error completing polymake line" exception=current_exceptions()
return String[], "", true
end
end


function CreatePolymakeREPL(; prompt = Polymake.get_current_app() * " > ", name = :pm, repl = Base.active_repl, main_mode = repl.interface.modes[1])
function CreatePolymakeREPL(; prompt = _get_prompt(), name = :pm, repl = Base.active_repl, main_mode = repl.interface.modes[1])
mirepl = isdefined(repl,:mi) ? repl.mi : repl
# Setup polymake panel
panel = LineEdit.Prompt(prompt;
# Copy colors from the prompt object
prompt_prefix=_color(prompt),
prompt_suffix=Base.text_colors[:white],
on_enter = REPL.return_callback)
prompt_prefix=_get_prompt_prefix(),
prompt_suffix=_get_prompt_suffix(),
on_enter = (_) -> true,
sticky = true)
#on_enter = s->isExpressionComplete(C,push!(copy(LineEdit.buffer(s).data),0)))

panel.on_done = REPL.respond(repl,panel; pass_empty = false) do line
if !isempty(line)
:(Polymake.shell_execute_print($line, $panel) )
:( Polymake.shell_execute_print($line, $panel) )
else
:( )
:( )
end
end

Expand All @@ -86,31 +102,70 @@ function CreatePolymakeREPL(; prompt = Polymake.get_current_app() * " > ", name
panel
end

global function run_polymake_repl(;
prompt = Polymake.get_current_app() * " > ",
function run_polymake_repl(repl = Base.active_repl;
prompt = _get_prompt(),
name = :pm,
key = '$')
repl = Base.active_repl
mirepl = isdefined(repl,:mi) ? repl.mi : repl
# skip repl init if it is not fully loaded
isdefined(mirepl, :interface) && isdefined(mirepl.interface, :modes) || return nothing
main_mode = mirepl.interface.modes[1]

panel = CreatePolymakeREPL(; prompt=prompt, name=name, repl=repl)

# Install this mode into the main mode
pm_keymap = Dict{Any,Any}(
key => function (s,args...)
if isempty(s) || position(LineEdit.buffer(s)) == 0
buf = copy(LineEdit.buffer(s))
LineEdit.transition(s, panel) do
LineEdit.state(s, panel).input_buffer = buf
end
else
LineEdit.edit_insert(s,key)
end
end
)
main_mode.keymap_dict = LineEdit.keymap_merge(main_mode.keymap_dict, pm_keymap);
nothing
key = '$',
nothrow = false)
try
repl isa REPL.LineEditREPL || error("only minimal REPL active, cannot add REPL mode, check DEPOT_PATH for stdlib path")
mirepl = isdefined(repl,:mi) ? repl.mi : repl
# skip repl init if it is not fully loaded
isdefined(mirepl, :interface) && isdefined(mirepl.interface, :modes) || return nothing
main_mode = mirepl.interface.modes[1]

panel = CreatePolymakeREPL(; prompt=prompt, name=name, repl=repl)
global polymakerepl[] = panel

# Install this mode into the main mode
pm_keymap = Dict{Any,Any}(
key => function (s,args...)
if isempty(s) || position(LineEdit.buffer(s)) == 0
buf = copy(LineEdit.buffer(s))
LineEdit.transition(s, panel) do
LineEdit.state(s, panel).input_buffer = buf
end
else
LineEdit.edit_insert(s,key)
end
end
)
main_mode.keymap_dict = LineEdit.keymap_merge(main_mode.keymap_dict, pm_keymap)
catch ex
if nothrow
# failing to initialize the repl should not be fatal during initialization
@warn ex
else
rethrow()
end
end
end

function try_init_polymake_repl()
if isdefined(Base, :active_repl)
if Base.active_repl isa REPL.LineEditREPL
run_polymake_repl(nothrow=true)
end
else
atreplinit() do repl
if isinteractive() && repl isa REPL.LineEditREPL
run_polymake_repl(nothrow=true)
end
end
end
end

function prompt()
if !isassigned(polymakerepl)
run_polymake_repl()
end
mist = Base.active_repl.mistate
pmr = polymakerepl[]
# hide prompt to avoid duplicate prompt printout during transition
pmr.prompt=""
REPL.transition(mist, pmr) do
LineEdit.state(mist, pmr).input_buffer = IOBuffer()
end
pmr.prompt=_get_prompt()
return nothing
end

0 comments on commit a598c97

Please sign in to comment.