Skip to content

Commit

Permalink
✂ Separate modules for different notebooks
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp committed Mar 23, 2020
1 parent 0ee82d7 commit a381ce8
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = "Pluto"
uuid = "c3e4b0f8-55cb-11ea-2926-15256bba5781"
license = "MIT"
authors = ["Fons van der Plas <[email protected]>", "Mikołaj Bochenski <[email protected]>"]
version = "0.3.4"
version = "0.3.5"

[deps]
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ Your notebooks are saved as pure Julia files, which you can then import as if yo

_(To developers: follow [these instructions](https://github.com/fonsp/Pluto.jl/blob/master/dev_instructions.md) to start working on the package.)_

To add the package:
After [installing julia](https://julialang.org/), add the package:
```julia
julia> using Pkg; Pkg.add(PackageSpec(url="https://github.com/fonsp/Pluto.jl"))
julia> ]
(v1.0) pkg> add Pluto
```

To run the notebook server:
Expand Down
45 changes: 32 additions & 13 deletions src/react/ModuleManager.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
module ModuleManager
import UUIDs: UUID
import ..Pluto: Notebook

"These expressions get executed whenever a new workspace is created."
workspace_preamble = [:(using Markdown), :(ENV["GKSwstype"] = "nul")]

workspace_count = 0
workspace_counts = Dict{UUID,Int64}()
next_count() = maximum(values(workspace_counts) [0]) + 1

function get_workspace_id(notebook::Notebook)
if haskey(workspace_counts, notebook.uuid)
workspace_counts[notebook.uuid]
else
make_workspace(notebook)
end
end

get_workspace(id=workspace_count) = Core.eval(ModuleManager, Symbol("workspace", id))
function get_workspace_at(id::Int64)
Core.eval(ModuleManager, Symbol("workspace", id))
end

function get_workspace(notebook::Notebook)
get_workspace_at(get_workspace_id(notebook))
end

function make_workspace()
global workspace_count += 1
function make_workspace(notebook::Notebook)
id = workspace_counts[notebook.uuid] = next_count()

new_workspace_name = Symbol("workspace", workspace_count)
new_workspace_name = Symbol("workspace", id)
workspace_creation = :(module $(new_workspace_name) $(workspace_preamble...) end)

# We suppress this warning:
Expand All @@ -26,15 +44,16 @@ module ModuleManager
redirect_stderr(original_stderr)
close(wr)
close(rd)

id
end
make_workspace() # so that there's immediately something to work with

forbiddenmove(sym::Symbol) = sym == :eval || sym == :include || string(sym)[1] == '#'

function move_vars(old_index::Integer, new_index::Integer, to_delete::Set{Symbol}=Set{Symbol}(), module_usings::Set{Expr}=Set{Expr}())
old_workspace = get_workspace(old_index)
function move_vars(notebook::Notebook, old_index::Integer, new_index::Integer, to_delete::Set{Symbol}=Set{Symbol}(), module_usings::Set{Expr}=Set{Expr}())
old_workspace = get_workspace_at(old_index)
old_workspace_name = Symbol("workspace", old_index)
new_workspace = get_workspace(new_index)
new_workspace = get_workspace_at(new_index)
new_workspace_name = Symbol("workspace", new_index)
Core.eval(new_workspace, :(import ..($(old_workspace_name))))

Expand All @@ -57,11 +76,11 @@ module ModuleManager
end
end

function delete_vars(to_delete::Set{Symbol}=Set{Symbol}(), module_usings::Set{Expr}=Set{Expr}())
function delete_vars(notebook::Notebook, to_delete::Set{Symbol}=Set{Symbol}(), module_usings::Set{Expr}=Set{Expr}())
if !isempty(to_delete)
old_index = workspace_count
make_workspace()
move_vars(old_index, old_index+1, to_delete, module_usings)
old_index = get_workspace_id(notebook)
new_index = make_workspace(notebook)
move_vars(notebook, old_index, new_index, to_delete, module_usings)
end
end
end
2 changes: 1 addition & 1 deletion src/react/Notebook.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function load_notebook(io, path)
# Change windows line endings to linux; remove the cell appendix.
code_normalised = replace(code, "\r\n" => "\n")[1:end - ncodeunits(_cell_appendix)]

read_cell = Cell(uuid, code_normalised, nothing, nothing, nothing, Set{Symbol}(), Set{Symbol}(), Set{Expr}())
read_cell = Cell(uuid, code_normalised, nothing, nothing, missing, nothing, Set{Symbol}(), Set{Symbol}(), Set{Expr}())

collected_cells[uuid] = read_cell
end
Expand Down
9 changes: 4 additions & 5 deletions src/react/React.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function run_reactive!(initiator, notebook::Notebook, cell::Cell)
module_usings = union((c.module_usings for c in notebook.cells)...)
to_delete = union(old_modified, (c.modified_symbols for c in will_update)...)

ModuleManager.delete_vars(to_delete, module_usings)
ModuleManager.delete_vars(notebook, to_delete, module_usings)

cell.modified_symbols = symstate.assignments

Expand All @@ -49,23 +49,22 @@ function run_reactive!(initiator, notebook::Notebook, cell::Cell)
end
relay_error!(to_run, "Cyclic references: $(join(modified_cyclic, ", ", " and "))")
else
run_single!(to_run)
run_single!(initiator, notebook, to_run)
end
putnotebookupdates!(notebook, clientupdate_cell_output(initiator, notebook, to_run))
# sleep(0.001)
end

return will_update
end


function run_single!(cell::Cell)
function run_single!(initiator, notebook::Notebook, cell::Cell)
# if isa(cell.parsedcode, Expr) && cell.parsedcode.head == :using
# # Don't run this cell. We set its output directly and stop the method prematurely.
# relay_error!(cell, "Use `import` instead of `using`.\nSupport for `using` will be added soon.")
# return
# end
workspace = ModuleManager.get_workspace()
workspace = ModuleManager.get_workspace(notebook)
starttime = time_ns()
try
starttime = time_ns()
Expand Down
12 changes: 7 additions & 5 deletions src/webserver/NotebookServer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ function flushclient(client)
didsomething = true

try
if isopen(client.stream)
write(client.stream, serialize_message(next_to_send))
else
@info "Client $(client.id) stream closed."
return false
if client.stream !== nothing
if isopen(client.stream)
write(client.stream, serialize_message(next_to_send))
else
@info "Client $(client.id) stream closed."
return false
end
end
catch e
@warn "Failed to write to WebSocket of $(client.id) " e
Expand Down
26 changes: 26 additions & 0 deletions test/ModuleManager.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@testset "Module manager" begin
# basic functionality is already tested by the reactivity tests

@testset "Multiple notebooks" begin
fakeclientA = Client(:fakeA, nothing)
fakeclientB = Client(:fakeB, nothing)
Pluto.connectedclients[fakeclientA.id] = fakeclientA
Pluto.connectedclients[fakeclientB.id] = fakeclientB


notebookA = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("x = 3")
])
fakeclientA.connected_notebook = notebookA

notebookB = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("x")
])
fakeclientB.connected_notebook = notebookB

@test_nowarn run_reactive!(fakeclientA, notebookA, notebookA.cells[1])
@test_nowarn run_reactive!(fakeclientB, notebookB, notebookB.cells[1])

@test notebookB.cells[1].errormessage !== nothing
end
end
41 changes: 26 additions & 15 deletions test/React.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using Test
using Pluto
import Pluto: Notebook, Client, run_reactive!,fakeclient, createcell_fromcode

import Pluto: Notebook, Client, run_reactive!,fakeclient, createcell_fromcode, ModuleManager

@testset "Reactivity" begin
fakeclient = Client(:fake, nothing)
Pluto.connectedclients[fakeclient.id] = fakeclient

@testset "Basic" begin
notebook = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("x = 1"),
createcell_fromcode("y = x")
])
createcell_fromcode("x = 1"),
createcell_fromcode("y = x")
])
fakeclient.connected_notebook = notebook

run_reactive!(fakeclient, notebook, notebook.cells[1])
run_reactive!(fakeclient, notebook, notebook.cells[2])
@test notebook.cells[1].output == notebook.cells[2].output
Expand All @@ -20,9 +23,11 @@ import Pluto: Notebook, Client, run_reactive!,fakeclient, createcell_fromcode

@testset "Cyclic" begin
notebook = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("x = y"),
createcell_fromcode("y = x")
])
createcell_fromcode("x = y"),
createcell_fromcode("y = x")
])
fakeclient.connected_notebook = notebook

run_reactive!(fakeclient, notebook, notebook.cells[1])
run_reactive!(fakeclient, notebook, notebook.cells[2])
@test occursin("Cyclic reference", notebook.cells[1].errormessage)
Expand All @@ -31,9 +36,11 @@ import Pluto: Notebook, Client, run_reactive!,fakeclient, createcell_fromcode

@testset "Variable deletion" begin
notebook = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("x = 1"),
createcell_fromcode("y = x")
])
createcell_fromcode("x = 1"),
createcell_fromcode("y = x")
])
fakeclient.connected_notebook = notebook

run_reactive!(fakeclient, notebook, notebook.cells[1])
run_reactive!(fakeclient, notebook, notebook.cells[2])
@test notebook.cells[1].output == notebook.cells[2].output
Expand All @@ -47,17 +54,21 @@ import Pluto: Notebook, Client, run_reactive!,fakeclient, createcell_fromcode

@testset "Recursive function is not considered cyclic" begin
notebook = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("factorial(n) = n * factorial(n-1)")
])
createcell_fromcode("factorial(n) = n * factorial(n-1)")
])
fakeclient.connected_notebook = notebook

run_reactive!(fakeclient, notebook, notebook.cells[1])
@test !isempty(methods(notebook.cells[1].output))
@test notebook.cells[1].errormessage == nothing
end

@testset "Variable cannot reference its previous value" begin
notebook = Notebook(joinpath(tempdir(), "test.jl"), [
createcell_fromcode("x = 3")
])
createcell_fromcode("x = 3")
])
fakeclient.connected_notebook = notebook

run_reactive!(fakeclient, notebook, notebook.cells[1])
notebook.cells[1].code = "x = x + 1"
run_reactive!(fakeclient, notebook, notebook.cells[1])
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include("./ExploreExpression.jl")
include("./React.jl")
include("./ModuleManager.jl")
include("./Notebook.jl")

3 comments on commit a381ce8

@fonsp
Copy link
Owner Author

@fonsp fonsp commented on a381ce8 Mar 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#30

@fonsp
Copy link
Owner Author

@fonsp fonsp commented on a381ce8 Mar 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/11393

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if Julia TagBot is installed, or can be done manually through the github interface, or via:

git tag -a v0.3.5 -m "<description of version>" a381ce80a70488971e52f00bc06880a9064fc6cf
git push origin v0.3.5

Please sign in to comment.