Skip to content

Commit f680b15

Browse files
authored
Merge pull request #17 from JuliaComputing/pg/PlutoEditor
2 parents 0f8ca42 + 1d326dc commit f680b15

File tree

9 files changed

+773
-265
lines changed

9 files changed

+773
-265
lines changed

julia-runtime/run.jl

Lines changed: 117 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
11
####
22
@info "COMMAND LINE ARGUMENTS"
33

4-
asset_output_dir, vscode_proxy_root_raw, port_str, secret, pluto_launch_params = if isempty(ARGS)
4+
asset_output_dir, port_str, secret, pluto_launch_params = if isempty(ARGS)
55
@warn "No arguments given, using development defaults!"
66
mktempdir(cleanup=false), "", "4653", "asdf", "{}"
77
else
88
ARGS
99
end
1010
port = parse(Int, port_str)
11-
vscode_proxy_root = let s = vscode_proxy_root_raw
12-
if isempty(s) || endswith(s, "/")
13-
s
14-
else
15-
s * "/"
16-
end
17-
end
1811

1912

2013
####
2114
@info "PLUTO SETUP"
22-
15+
using Base64
2316
import Pkg
2417
Pkg.instantiate()
2518

2619
import JSON
2720
using Suppressor
21+
using UUIDs
2822

2923
import Pluto
3024

25+
#= These are the function which document how we communicate, through STDIN
26+
# with the extension =#
27+
function getNextSTDINCommand()
28+
new_command_str_raw = readuntil(stdin, '\0')
29+
new_command_str = strip(new_command_str_raw, ['\0'])
30+
JSON.parse(new_command_str)
31+
end
32+
33+
function sendSTDERRCommand(name::String, payload::String)
34+
io = IOBuffer()
35+
io64 = Base64EncodePipe(io)
36+
print(io64, payload)
37+
close(io64)
38+
@info "Command: [[Notebook=$(name)]] ## $(String(take!(io))) ###"
39+
end
40+
41+
# This is the definition of type piracy
42+
@Base.kwdef struct PlutoExtensionSessionData
43+
textRepresentations:: Dict{String, String}
44+
notebooks::Dict{String, Pluto.Notebook}
45+
session::Pluto.ServerSession
46+
session_options::Any
47+
jlfilesroot::String
48+
end
49+
50+
# We spin up Pluto from here.
3151
pluto_server_options = Pluto.Configuration.from_flat_kwargs(;
3252
port=port,
3353
launch_browser=false,
@@ -42,17 +62,37 @@ pluto_server_session = Pluto.ServerSession(;
4262
options=pluto_server_options,
4363
)
4464

65+
extensionData = PlutoExtensionSessionData(
66+
Dict(),
67+
Dict(),
68+
pluto_server_session,
69+
pluto_server_options,
70+
joinpath(asset_output_dir, "jlfiles/")
71+
)
4572

46-
###
47-
@info "OPEN NOTEBOOK"
48-
49-
73+
function whenNotebookUpdates(jlfile, newString)
74+
filename = splitpath(jlfile)[end]
75+
sendSTDERRCommand(filename, newString)
76+
end
5077

78+
# This is the definition of Type Piracy 😇
79+
function Pluto.save_notebook(notebook::Pluto.Notebook)
80+
oldRepr = get(extensionData.textRepresentations, notebook.path, "")
81+
newRepr = sprint() do io
82+
Pluto.save_notebook(io, notebook)
83+
end
84+
if newRepr != oldRepr
85+
extensionData.textRepresentations[notebook.path] = newRepr
86+
whenNotebookUpdates(notebook.path, newRepr)
87+
end
88+
end
5189

90+
###
91+
@info "OPEN NOTEBOOK"
5292

5393
####
5494

55-
function generate_output(nb::Pluto.Notebook, filename::String, frontend_params::Dict=Dict())
95+
function generate_output(nb::Pluto.Notebook, filename::String, vscode_proxy_root::String, frontend_params::Dict=Dict())
5696
@info "GENERATING HTML FOR BESPOKE EDITOR" string(nb.notebook_id)
5797
new_editor_contents = Pluto.generate_html(;
5898
pluto_cdn_root = vscode_proxy_root,
@@ -67,56 +107,90 @@ function generate_output(nb::Pluto.Notebook, filename::String, frontend_params::
67107
end
68108

69109

70-
copy_assets() = cp(Pluto.project_relative_path("frontend"), asset_output_dir; force=true)
110+
function copy_assets()
111+
mkpath(asset_output_dir)
112+
src = Pluto.project_relative_path("frontend")
113+
dest = asset_output_dir
114+
for f in readdir(src)
115+
cp(joinpath(src, f), joinpath(dest, f); force=true)
116+
end
117+
end
71118

72119
copy_assets()
73-
74-
75-
try
76-
import BetterFileWatching
77-
@async try
78-
BetterFileWatching.watch_folder(Pluto.project_relative_path("frontend")) do event
79-
@info "Pluto asset changed!"
80-
copy_assets()
120+
mkpath(extensionData.jlfilesroot)
121+
122+
try ## Note: This is to assist with co-developing Pluto & this Extension
123+
## In a production setting it's not necessary to watch pluto folder for updates
124+
import BetterFileWatching
125+
@async try
126+
BetterFileWatching.watch_folder(Pluto.project_relative_path("frontend")) do event
127+
@info "Pluto asset changed!"
128+
# It's not safe to remove the folder
129+
# because we reuse HTML files
130+
copy_assets()
131+
mkpath(joinpath(asset_output_dir, "jlfiles/"))
132+
end
133+
catch e
134+
showerror(stderr, e, catch_backtrace())
81135
end
82-
catch e
83-
showerror(stderr, e, catch_backtrace())
84-
end
85-
@info "Watching Pluto folder for changes!"
136+
@info "Watching Pluto folder for changes!"
86137
catch end
87138

88-
89-
90-
91139
command_task = Pluto.@asynclog while true
92-
93-
new_command_str_raw = readuntil(stdin, '\0')
94-
new_command_str = strip(new_command_str_raw, ['\0'])
95-
new_command = JSON.parse(new_command_str)
140+
filenbmap = extensionData.notebooks
141+
new_command = getNextSTDINCommand()
96142

97143
@info "New command received!" new_command
98144

99145
type = get(new_command, "type", "")
100146
detail = get(new_command, "detail", Dict())
101147

102-
if type == "new"
148+
149+
if type == "open"
103150
editor_html_filename = detail["editor_html_filename"]
104-
nb = Pluto.SessionActions.new(pluto_server_session)
151+
vscode_proxy_root = let
152+
s = get(detail, "vscode_proxy_root", "not given")
153+
if isempty(s) || endswith(s, "/")
154+
s
155+
else
156+
s * "/"
157+
end
158+
end
159+
frontend_params = get(detail, "frontend_params", Dict())
105160

106-
generate_output(nb, editor_html_filename)
107-
elseif type == "open"
108-
editor_html_filename = detail["editor_html_filename"]
109-
nb = Pluto.SessionActions.open(pluto_server_session, detail["path"])
110161

111-
generate_output(nb, editor_html_filename)
162+
jlpath = joinpath(extensionData.jlfilesroot, detail["jlfile"])
163+
extensionData.textRepresentations[detail["jlfile"]] = detail["text"]
164+
open(jlpath, "w") do f
165+
write(f, detail["text"])
166+
end
167+
nb = Pluto.SessionActions.open(pluto_server_session, jlpath; notebook_id=UUID(detail["notebook_id"]))
168+
filenbmap[detail["jlfile"]] = nb
169+
generate_output(nb, editor_html_filename, vscode_proxy_root, frontend_params)
170+
171+
elseif type == "update"
172+
nb = filenbmap[detail["jlfile"]]
173+
jlpath = joinpath(extensionData.jlfilesroot, detail["jlfile"])
174+
open(jlpath, "w") do f
175+
write(f, detail["text"])
176+
end
177+
Pluto.update_from_file(pluto_server_session, nb)
178+
extensionData.textRepresentations[detail["jlfile"]] = detail["text"]
179+
180+
elseif type == "shutdown"
181+
nb = get(filenbmap, detail["jlfile"], nothing);
182+
!isnothing(nb) && Pluto.SessionActions.shutdown(
183+
pluto_server_session,
184+
nb,
185+
keep_in_session=false
186+
)
187+
112188
else
113189
@error "Message of this type not recognised. " type
114190
end
115191

116192
end
117193

118-
119-
120194
####
121195
@info "RUNNING PLUTO SERVER..."
122196
@info "MESSAGE TO EXTENSION: READY FOR COMMANDS"

package.json

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,56 @@
1515
"Other"
1616
],
1717
"activationEvents": [
18-
"onCommand:plutoView.start",
19-
"onWebviewPanel:plutoView"
18+
"onCommand:plutoEditor.start",
19+
"onCommand:plutoEditor.openCurrentWith",
20+
"onWebviewPanel:plutoEditor",
21+
"onCustomEditor:plutoEditor"
2022
],
2123
"main": "./out/extension.js",
2224
"contributes": {
2325
"commands": [
2426
{
25-
"command": "plutoView.start",
26-
"title": "Start new notebook",
27+
"command": "plutoEditor.start",
28+
"title": "Start a Pluto.jl notebook 🎈",
2729
"category": "Pluto"
30+
},
31+
{
32+
"command": "plutoEditor.openCurrentWith",
33+
"title": "Open with Pluto.jl 🎈",
34+
"category": "Pluto"
35+
}
36+
],
37+
"customEditors": [
38+
{
39+
"viewType": "plutoEditor",
40+
"displayName": "Pluto",
41+
"selector": [
42+
{
43+
"filenamePattern": "*.jl"
44+
},
45+
{
46+
"filenamePattern": "*.plutojl"
47+
}
48+
],
49+
"priority": "option"
2850
}
29-
]
51+
],
52+
"menus": {
53+
"editor/title/context": [
54+
{
55+
"when": "resourceLangId == julia",
56+
"command": "plutoEditor.openCurrentWith",
57+
"group": "3_open@3"
58+
}
59+
],
60+
"explorer/context": [
61+
{
62+
"when": "resourceLangId == julia",
63+
"command": "plutoEditor.openCurrentWith",
64+
"group": "navigation@4"
65+
}
66+
]
67+
}
3068
},
3169
"scripts": {
3270
"vscode:prepublish": "npm run compile",

0 commit comments

Comments
 (0)