Line | Exclusive | Inclusive | Code |
---|---|---|---|
1 | # Most of the code here is copied from the Juno codebase | ||
2 | |||
3 | using REPL | ||
4 | using REPL.LineEdit | ||
5 | |||
6 | const ENABLE_SHELL_INTEGRATION = Ref{Bool}(false) | ||
7 | |||
8 | isREPL() = isdefined(Base, :active_repl) && | ||
9 | isdefined(Base.active_repl, :interface) && | ||
10 | isdefined(Base.active_repl.interface, :modes) && | ||
11 | isdefined(Base.active_repl, :mistate) && | ||
12 | isdefined(Base.active_repl.mistate, :current_mode) | ||
13 | |||
14 | juliaprompt = "julia> " | ||
15 | |||
16 | current_prompt = juliaprompt | ||
17 | |||
18 | function get_main_mode(repl = Base.active_repl) | ||
19 | mode = repl.interface.modes[1] | ||
20 | mode isa LineEdit.Prompt || error("no julia repl mode found") | ||
21 | mode | ||
22 | end | ||
23 | |||
24 | function hideprompt(f) | ||
25 | isREPL() || return f() | ||
26 | |||
27 | repl = Base.active_repl | ||
28 | mistate = repl.mistate | ||
29 | mode = mistate.current_mode | ||
30 | |||
31 | buf = String(take!(copy(LineEdit.buffer(mistate)))) | ||
32 | |||
33 | # clear input buffer | ||
34 | truncate(LineEdit.buffer(mistate), 0) | ||
35 | LineEdit.refresh_multi_line(mistate) | ||
36 | |||
37 | print(stdout, "\e[1K\r") | ||
38 | 288 (100 %) |
288 (100 %)
samples spent calling
#66
r = f()
|
|
39 | |||
40 | flush(stdout) | ||
41 | flush(stderr) | ||
42 | sleep(0.05) | ||
43 | |||
44 | # TODO Fix this | ||
45 | # pos = @rpc cursorpos() | ||
46 | pos = 1, 1 | ||
47 | pos[1] != 0 && println() | ||
48 | |||
49 | # restore prompt | ||
50 | if applicable(LineEdit.write_prompt, stdout, mode) | ||
51 | LineEdit.write_prompt(stdout, mode) | ||
52 | elseif applicable(LineEdit.write_prompt, stdout, mode, true) | ||
53 | LineEdit.write_prompt(stdout, mode, true) | ||
54 | elseif mode isa LineEdit.PrefixHistoryPrompt || :parent_prompt in fieldnames(typeof(mode)) | ||
55 | if applicable(LineEdit.write_prompt, stdout, mode.parent_prompt) | ||
56 | LineEdit.write_prompt(stdout, mode.parent_prompt) | ||
57 | elseif applicable(LineEdit.write_prompt, stdout, mode.parent_prompt, true) | ||
58 | LineEdit.write_prompt(stdout, mode.parent_prompt, true) | ||
59 | else | ||
60 | printstyled(stdout, current_prompt, color = :green, bold = true) | ||
61 | end | ||
62 | else | ||
63 | printstyled(stdout, current_prompt, color = :green, bold = true) | ||
64 | end | ||
65 | |||
66 | truncate(LineEdit.buffer(mistate), 0) | ||
67 | |||
68 | # restore input buffer | ||
69 | LineEdit.edit_insert(LineEdit.buffer(mistate), buf) | ||
70 | LineEdit.refresh_multi_line(mistate) | ||
71 | r | ||
72 | end | ||
73 | |||
74 | si(f) = (args...) -> ENABLE_SHELL_INTEGRATION[] ? f(args...) : "" | ||
75 | |||
76 | function sanitize_shell_integration_string(cmd) | ||
77 | replace(replace(replace(cmd, "\n" => "<LF>"), ";" => "<CL>"), "\a" => "<ST>") | ||
78 | end | ||
79 | |||
80 | const SHELL = ( | ||
81 | prompt_start = si(() -> "\e]633;A\a"), | ||
82 | prompt_end = si(() -> "\e]633;B\a"), | ||
83 | output_start = si(() -> "\e]633;C\a"), | ||
84 | output_end = si(function () | ||
85 | if REPL_PROMPT_STATE[] === REPLPromptStates.NoUpdate | ||
86 | return "" | ||
87 | elseif REPL_PROMPT_STATE[] === REPLPromptStates.NoStatus | ||
88 | REPL_PROMPT_STATE[] = REPLPromptStates.NoUpdate | ||
89 | return "\e]633;D\a" | ||
90 | else | ||
91 | exitcode = REPL_PROMPT_STATE[] == REPLPromptStates.Error | ||
92 | REPL_PROMPT_STATE[] = REPLPromptStates.NoUpdate | ||
93 | return "\e]633;D;$(Int(exitcode))\a" | ||
94 | end | ||
95 | end), | ||
96 | update_cmd = si(function (cmd) | ||
97 | cmd = sanitize_shell_integration_string(cmd) | ||
98 | "\e]633;E;$cmd\a" | ||
99 | end), | ||
100 | continuation_prompt_start = si(() -> "\e]633;F\a"), | ||
101 | continuation_prompt_end = si(() -> "\e]633;G\a"), | ||
102 | update_cwd = si(() -> "\e]633;P;Cwd=$(pwd())\a"), | ||
103 | windows_compat = si(() -> "\e]633;P;IsWindows=True\a") | ||
104 | ) | ||
105 | |||
106 | as_func(x) = () -> x | ||
107 | as_func(x::Function) = x | ||
108 | |||
109 | function install_vscode_shell_integration(prompt) | ||
110 | if Sys.iswindows() | ||
111 | print(stdout, SHELL.windows_compat()) | ||
112 | end | ||
113 | prefix = as_func(prompt.prompt_prefix) | ||
114 | suffix = as_func(prompt.prompt_suffix) | ||
115 | prompt.prompt_prefix = () -> string(SHELL.output_end(), SHELL.prompt_start(), prefix()) | ||
116 | prompt.prompt_suffix = () -> string(suffix(), SHELL.update_cwd(), SHELL.prompt_end()) | ||
117 | |||
118 | on_done = prompt.on_done | ||
119 | prompt.on_done = function (mi, buf, ok) | ||
120 | print(stdout, SHELL.output_start(), SHELL.update_cmd(String(take!(deepcopy(buf))))) | ||
121 | REPL_PROMPT_STATE[] = REPLPromptStates.NoStatus | ||
122 | on_done(mi, buf, ok) | ||
123 | end | ||
124 | end | ||
125 | |||
126 | if VERSION > v"1.9-" | ||
127 | active_module = Base.active_module | ||
128 | else | ||
129 | active_module() = Main | ||
130 | end | ||
131 | |||
132 | const HAS_REPL_TRANSFORM = Ref{Bool}(false) | ||
133 | function hook_repl(repl) | ||
134 | if HAS_REPL_TRANSFORM[] | ||
135 | return | ||
136 | end | ||
137 | @debug "installing REPL hook" | ||
138 | if !isdefined(repl, :interface) | ||
139 | repl.interface = REPL.setup_interface(repl) | ||
140 | end | ||
141 | main_mode = get_main_mode(repl) | ||
142 | |||
143 | if VERSION > v"1.5-" | ||
144 | for _ = 1:20 # repl backend should be set up after 10s -- fall back to the pre-ast-transform approach otherwise | ||
145 | isdefined(Base, :active_repl_backend) && continue | ||
146 | sleep(0.5) | ||
147 | end | ||
148 | if isdefined(Base, :active_repl_backend) | ||
149 | push!(Base.active_repl_backend.ast_transforms, ast -> transform_backend(ast, repl, main_mode)) | ||
150 | HAS_REPL_TRANSFORM[] = true | ||
151 | install_vscode_shell_integration(main_mode) | ||
152 | @debug "REPL AST transform installed" | ||
153 | return | ||
154 | end | ||
155 | end | ||
156 | |||
157 | main_mode.on_done = REPL.respond(repl, main_mode; pass_empty = false) do line | ||
158 | quote | ||
159 | $(evalrepl)($(active_module)(), $line, $repl, $main_mode) | ||
160 | end | ||
161 | end | ||
162 | @debug "legacy REPL hook installed" | ||
163 | HAS_REPL_TRANSFORM[] = true | ||
164 | return nothing | ||
165 | end | ||
166 | |||
167 | function transform_backend(ast, repl, main_mode) | ||
168 | quote | ||
169 | $(evalrepl)($(active_module)(), $(QuoteNode(ast)), $repl, $main_mode) | ||
170 | end | ||
171 | end | ||
172 | |||
173 | const REPLPromptStates = ( | ||
174 | NoUpdate = 0, | ||
175 | NoStatus = 1, | ||
176 | Success = 2, | ||
177 | Error = 3, | ||
178 | ) | ||
179 | |||
180 | const REPL_PROMPT_STATE = Ref{Int}(REPLPromptStates.NoUpdate) | ||
181 | function evalrepl(m, line, repl, main_mode) | ||
182 | did_notify = false | ||
183 | return try | ||
184 | try | ||
185 | JSONRPC.send_notification(conn_endpoint[], "repl/starteval", nothing) | ||
186 | did_notify = true | ||
187 | catch err | ||
188 | @debug "Could not send repl/starteval notification" exception = (err, catch_backtrace()) | ||
189 | end | ||
190 | r = run_with_backend() do | ||
191 | fix_displays(; is_repl = true) | ||
192 | f = () -> repleval(m, line, REPL.repl_filename(repl, main_mode.hist)) | ||
193 | PROGRESS_ENABLED[] ? Logging.with_logger(f, VSCodeLogger()) : f() | ||
194 | end | ||
195 | REPL_PROMPT_STATE[] = REPLPromptStates.Error | ||
196 | if r isa EvalError | ||
197 | display_repl_error(stderr, r.err, r.bt) | ||
198 | nothing | ||
199 | elseif r isa EvalErrorStack | ||
200 | set_error_global(r) | ||
201 | display_repl_error(stderr, r) | ||
202 | nothing | ||
203 | else | ||
204 | REPL_PROMPT_STATE[] = REPLPromptStates.Success | ||
205 | r | ||
206 | end | ||
207 | catch err | ||
208 | # This is for internal errors only. | ||
209 | Base.display_error(stderr, err, catch_backtrace()) | ||
210 | nothing | ||
211 | finally | ||
212 | if did_notify | ||
213 | try | ||
214 | JSONRPC.send_notification(conn_endpoint[], "repl/finisheval", nothing) | ||
215 | catch err | ||
216 | @debug "Could not send repl/finisheval notification" exception = (err, catch_backtrace()) | ||
217 | end | ||
218 | end | ||
219 | end | ||
220 | end | ||
221 | |||
222 | # don't inline this so we can find it in the stacktrace | ||
223 | @noinline function repleval(m, code::String, file) | ||
224 | args = VERSION >= v"1.5" ? (REPL.softscope, m, code, file) : (m, code, file) | ||
225 | return include_string(args...) | ||
226 | end | ||
227 | |||
228 | @noinline function repleval(m, code, _) | ||
229 | return Base.eval(m, code) | ||
230 | end | ||
231 | |||
232 | replcontext(io, limitflag) = IOContext( | ||
233 | io, | ||
234 | :limit => true, | ||
235 | :displaysize => get(stdout, :displaysize, (60, 120)), | ||
236 | :stacktrace_types_limited => limitflag, | ||
237 | ) | ||
238 | |||
239 | # basically the same as Base's `display_error`, with internal frames removed | ||
240 | function display_repl_error(io, err, bt) | ||
241 | limitflag = Ref(false) | ||
242 | |||
243 | st = stacktrace(crop_backtrace(bt)) | ||
244 | printstyled(io, "ERROR: "; bold = true, color = Base.error_color()) | ||
245 | showerror(replcontext(io, limitflag), err, st) | ||
246 | if limitflag[] | ||
247 | print(io, "Some type information was truncated. Use `show(err)` to see complete types.") | ||
248 | end | ||
249 | println(io) | ||
250 | end | ||
251 | |||
252 | function display_repl_error(io, stack::EvalErrorStack) | ||
253 | limitflag = Ref(false) | ||
254 | |||
255 | printstyled(io, "ERROR: "; bold = true, color = Base.error_color()) | ||
256 | for (i, (err, bt)) in enumerate(reverse(stack.stack)) | ||
257 | i !== 1 && print(io, "\ncaused by: ") | ||
258 | st = stacktrace(crop_backtrace(bt)) | ||
259 | showerror(replcontext(io, limitflag), i == 1 ? unwrap_loaderror(err) : err, st) | ||
260 | println(io) | ||
261 | end | ||
262 | |||
263 | if limitflag[] | ||
264 | println(io, "Some type information was truncated. Use `show(err)` to see complete types.") | ||
265 | end | ||
266 | end | ||
267 | |||
268 | function withpath(f, path) | ||
269 | tls = task_local_storage() | ||
270 | hassource = haskey(tls, :SOURCE_PATH) | ||
271 | hassource && (path′ = tls[:SOURCE_PATH]) | ||
272 | tls[:SOURCE_PATH] = path | ||
273 | try | ||
274 | 288 (100 %) |
288 (100 %)
samples spent calling
#67
return f()
|
|
275 | finally | ||
276 | hassource ? (tls[:SOURCE_PATH] = path′) : delete!(tls, :SOURCE_PATH) | ||
277 | end | ||
278 | end |