Skip to content
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

REPL: some improvements and docs #503

Merged
merged 6 commits into from
Dec 24, 2024
Merged
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
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 @@
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()

Check warning on line 84 in src/perlobj.jl

View check run for this annotation

Codecov / codecov/patch

src/perlobj.jl#L84

Added line #L84 was not covered by tests
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.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]

Check warning on line 25 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L21-L25

Added lines #L21 - L25 were not covered by tests

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)

Check warning on line 31 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L29-L31

Added lines #L29 - L31 were not covered by tests

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()

Check warning on line 37 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L36-L37

Added lines #L36 - L37 were not covered by tests
end
print(Base.stderr, res[3])
if !isempty(res[4])
error(res[4])
Expand All @@ -35,36 +44,43 @@
if !isempty(res[4])
error(res[4])
else
error("polymake: incomplete statement")
error("polymake: incomplete statement, try Alt+Enter for multi-line input")

Check warning on line 47 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L47

Added line #L47 was not covered by tests
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

Check warning on line 60 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L52-L60

Added lines #L52 - L60 were not covered by tests
catch
@debug "error completing polymake line" exception=current_exceptions()
return String[], "", true

Check warning on line 63 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L62-L63

Added lines #L62 - L63 were not covered by tests
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])

Check warning on line 68 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L68

Added line #L68 was not covered by tests
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,

Check warning on line 75 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L75

Added line #L75 was not covered by tests
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) )

Check warning on line 81 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L81

Added line #L81 was not covered by tests
else
:( )
:( )

Check warning on line 83 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L83

Added line #L83 was not covered by tests
end
end

Expand All @@ -86,31 +102,70 @@
panel
end

global function run_polymake_repl(;
prompt = Polymake.get_current_app() * " > ",
function run_polymake_repl(repl = Base.active_repl;

Check warning on line 105 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L105

Added line #L105 was not covered by tests
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

Check warning on line 112 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L110-L112

Added lines #L110 - L112 were not covered by tests
# skip repl init if it is not fully loaded
isdefined(mirepl, :interface) && isdefined(mirepl.interface, :modes) || return nothing
main_mode = mirepl.interface.modes[1]

Check warning on line 115 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L114-L115

Added lines #L114 - L115 were not covered by tests

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

Check warning on line 118 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L117-L118

Added lines #L117 - L118 were not covered by tests

# 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

Check warning on line 126 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L121-L126

Added lines #L121 - L126 were not covered by tests
end
else
LineEdit.edit_insert(s,key)

Check warning on line 129 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L129

Added line #L129 was not covered by tests
end
end
)
main_mode.keymap_dict = LineEdit.keymap_merge(main_mode.keymap_dict, pm_keymap)

Check warning on line 133 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L133

Added line #L133 was not covered by tests
catch ex
if nothrow

Check warning on line 135 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L135

Added line #L135 was not covered by tests
# failing to initialize the repl should not be fatal during initialization
@warn ex

Check warning on line 137 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L137

Added line #L137 was not covered by tests
else
rethrow()

Check warning on line 139 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L139

Added line #L139 was not covered by tests
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)

Check warning on line 147 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L146-L147

Added lines #L146 - L147 were not covered by tests
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()

Check warning on line 160 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L158-L160

Added lines #L158 - L160 were not covered by tests
end
mist = Base.active_repl.mistate
pmr = polymakerepl[]

Check warning on line 163 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L162-L163

Added lines #L162 - L163 were not covered by tests
# hide prompt to avoid duplicate prompt printout during transition
pmr.prompt=""
REPL.transition(mist, pmr) do
LineEdit.state(mist, pmr).input_buffer = IOBuffer()

Check warning on line 167 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L165-L167

Added lines #L165 - L167 were not covered by tests
end
pmr.prompt=_get_prompt()
return nothing

Check warning on line 170 in src/repl.jl

View check run for this annotation

Codecov / codecov/patch

src/repl.jl#L169-L170

Added lines #L169 - L170 were not covered by tests
end
Loading