Line | Exclusive | Inclusive | Code |
---|---|---|---|
1 | const INLINE_RESULT_LENGTH = 100 | ||
2 | const MAX_RESULT_LENGTH = 10_000 | ||
3 | |||
4 | # Workaround for https://github.com/julia-vscode/julia-vscode/issues/1940 | ||
5 | struct Wrapper | ||
6 | content::Any | ||
7 | end | ||
8 | wrap(x) = Wrapper(x) | ||
9 | unwrap(x) = x.content | ||
10 | |||
11 | const EVAL_CHANNEL_IN = Channel(0) | ||
12 | const EVAL_CHANNEL_OUT = Channel(0) | ||
13 | const EVAL_BACKEND_TASK = Ref{Any}(nothing) | ||
14 | const IS_BACKEND_WORKING = Ref{Bool}(false) | ||
15 | |||
16 | function is_evaling() | ||
17 | return IS_BACKEND_WORKING[] | ||
18 | end | ||
19 | |||
20 | function run_with_backend(f, args...) | ||
21 | put!(EVAL_CHANNEL_IN, (f, args)) | ||
22 | return unwrap(take!(EVAL_CHANNEL_OUT)) | ||
23 | end | ||
24 | |||
25 | function start_eval_backend() | ||
26 | global EVAL_BACKEND_TASK[] = @async begin | ||
27 | Base.sigatomic_begin() | ||
28 | while true | ||
29 | try | ||
30 | f, args = take!(EVAL_CHANNEL_IN) | ||
31 | Base.sigatomic_end() | ||
32 | IS_BACKEND_WORKING[] = true | ||
33 | res = try | ||
34 | 288 (100 %) |
288 (100 %)
samples spent calling
invokelatest
Base.invokelatest(f, args...)
|
|
35 | catch err | ||
36 | @static if isdefined(Base, :current_exceptions) | ||
37 | EvalErrorStack(Base.current_exceptions(current_task())) | ||
38 | elseif isdefined(Base, :catch_stack) | ||
39 | EvalErrorStack(Base.catch_stack()) | ||
40 | else | ||
41 | EvalError(err, catch_backtrace()) | ||
42 | end | ||
43 | end | ||
44 | IS_BACKEND_WORKING[] = false | ||
45 | Base.sigatomic_begin() | ||
46 | put!(EVAL_CHANNEL_OUT, wrap(res)) | ||
47 | catch err | ||
48 | put!(EVAL_CHANNEL_OUT, wrap(err)) | ||
49 | finally | ||
50 | IS_BACKEND_WORKING[] = false | ||
51 | end | ||
52 | end | ||
53 | Base.sigatomic_end() | ||
54 | end | ||
55 | end | ||
56 | |||
57 | function repl_interrupt_request(conn, ::Nothing) | ||
58 | println(stderr, "^C") | ||
59 | if EVAL_BACKEND_TASK[] !== nothing && !istaskdone(EVAL_BACKEND_TASK[]) && IS_BACKEND_WORKING[] | ||
60 | schedule(EVAL_BACKEND_TASK[], InterruptException(); error = true) | ||
61 | end | ||
62 | end | ||
63 | |||
64 | # https://github.com/JuliaLang/julia/blob/53a781d399bfb517b554fb1ae106e6dac99205f1/stdlib/REPL/src/REPL.jl#L547 | ||
65 | function add_code_to_repl_history(code) | ||
66 | code = strip(code) | ||
67 | isempty(code) && return | ||
68 | |||
69 | try | ||
70 | mode = get_main_mode() | ||
71 | hist = mode.hist | ||
72 | !isempty(hist.history) && | ||
73 | isequal(:julia, hist.modes[end]) && code == hist.history[end] && return | ||
74 | |||
75 | hist.last_mode = mode | ||
76 | hist.last_buffer = let | ||
77 | io = IOBuffer() | ||
78 | print(io, code) | ||
79 | io | ||
80 | end | ||
81 | push!(hist.modes, :julia) | ||
82 | push!(hist.history, code) | ||
83 | hist.history_file === nothing && return | ||
84 | entry = """ | ||
85 | # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time())) | ||
86 | # mode: julia | ||
87 | $(replace(code, r"^"ms => "\t")) | ||
88 | """ | ||
89 | seekend(hist.history_file) | ||
90 | print(hist.history_file, entry) | ||
91 | flush(hist.history_file) | ||
92 | |||
93 | hist.cur_idx = length(hist.history) + 1 | ||
94 | catch err | ||
95 | @error "writing to history failed" exception = (err, catch_backtrace()) | ||
96 | end | ||
97 | end | ||
98 | |||
99 | CAN_SET_ANS = Ref{Bool}(true) | ||
100 | CAN_SET_ERR = Ref{Bool}(true) | ||
101 | |||
102 | function set_error_global(errs) | ||
103 | if CAN_SET_ERR[] | ||
104 | try | ||
105 | errs isa EvalErrorStack || error() | ||
106 | istrivial = @static if isdefined(Base, :istrivialerror) | ||
107 | Base.istrivialerror(errs.stack) | ||
108 | else | ||
109 | true | ||
110 | end | ||
111 | @static if VERSION > v"1.10-" | ||
112 | istrivial || setglobal!(Base.MainInclude, :err, errs.stack) | ||
113 | elseif @isdefined setglobal! | ||
114 | istrivial || setglobal!(Main, :err, errs.stack) | ||
115 | else | ||
116 | istrivial || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, errs.stack) | ||
117 | end | ||
118 | catch | ||
119 | CAN_SET_ERR[] = false | ||
120 | end | ||
121 | end | ||
122 | end | ||
123 | |||
124 | function repl_runcode_request(conn, params::ReplRunCodeRequestParams)::ReplRunCodeRequestReturn | ||
125 | run_with_backend() do | ||
126 | fix_displays() | ||
127 | |||
128 | source_filename = params.filename | ||
129 | code_line = params.line | ||
130 | code_column = params.column | ||
131 | source_code = params.code | ||
132 | mod = params.mod | ||
133 | |||
134 | resolved_mod = try | ||
135 | module_from_string(mod) | ||
136 | catch err | ||
137 | # maybe trigger error reporting here | ||
138 | Main | ||
139 | end | ||
140 | |||
141 | show_code = params.showCodeInREPL | ||
142 | show_result = params.showResultInREPL | ||
143 | show_error = params.showErrorInREPL | ||
144 | try | ||
145 | JSONRPC.send_notification(conn_endpoint[], "repl/starteval", nothing) | ||
146 | catch err | ||
147 | @debug "Could not send repl/starteval notification." | ||
148 | end | ||
149 | |||
150 | 288 (100 %) |
288 (100 %)
samples spent calling
hideprompt
f = () -> hideprompt() do
|
|
151 | revise() | ||
152 | |||
153 | if show_code | ||
154 | add_code_to_repl_history(source_code) | ||
155 | |||
156 | prompt = "julia> " | ||
157 | prefix = string(SHELL.output_end(), SHELL.prompt_start(), "\e[32m\e[1m") | ||
158 | suffix = string("\e[0m", SHELL.update_cwd(), SHELL.prompt_end()) | ||
159 | try | ||
160 | mode = get_main_mode() | ||
161 | prompt = LineEdit.prompt_string(mode.prompt) | ||
162 | LineEdit.write_prompt(stdout, mode) | ||
163 | catch err | ||
164 | print(stdout, prefix, prompt, suffix) | ||
165 | @debug "getting prompt info failed" exception = (err, catch_backtrace()) | ||
166 | end | ||
167 | |||
168 | for (i, line) in enumerate(eachline(IOBuffer(source_code))) | ||
169 | i != 1 && print(' '^length(prompt)) | ||
170 | print(' '^code_column) | ||
171 | println(line) | ||
172 | end | ||
173 | |||
174 | print(stdout, SHELL.output_start()) | ||
175 | print(stdout, SHELL.update_cmd(source_code)) | ||
176 | REPL_PROMPT_STATE[] = REPLPromptStates.NoStatus | ||
177 | end | ||
178 | |||
179 | 288 (100 %) |
288 (100 %)
samples spent calling
withpath
return withpath(source_filename) do
|
|
180 | res = try | ||
181 | 288 (100 %) |
288 (100 %)
samples spent calling
inlineeval
val = inlineeval(resolved_mod, source_code, code_line, code_column, source_filename, softscope = params.softscope)
|
|
182 | if CAN_SET_ANS[] | ||
183 | try | ||
184 | @static if VERSION > v"1.10-" | ||
185 | setglobal!(Base.MainInclude, :ans, val) | ||
186 | elseif @isdefined setglobal! | ||
187 | setglobal!(Main, :ans, val) | ||
188 | else | ||
189 | ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, val) | ||
190 | end | ||
191 | catch _ | ||
192 | CAN_SET_ANS[] = false | ||
193 | end | ||
194 | end | ||
195 | if show_code | ||
196 | REPL_PROMPT_STATE[] = REPLPromptStates.Success | ||
197 | end | ||
198 | val | ||
199 | catch err | ||
200 | if show_code | ||
201 | REPL_PROMPT_STATE[] = REPLPromptStates.Error | ||
202 | end | ||
203 | errs = @static if isdefined(Base, :current_exceptions) | ||
204 | EvalErrorStack(Base.current_exceptions(current_task())) | ||
205 | elseif isdefined(Base, :catch_stack) | ||
206 | EvalErrorStack(Base.catch_stack()) | ||
207 | else | ||
208 | EvalError(err, catch_backtrace()) | ||
209 | end | ||
210 | |||
211 | set_error_global(errs) | ||
212 | |||
213 | errs | ||
214 | finally | ||
215 | try | ||
216 | JSONRPC.send_notification(conn_endpoint[], "repl/finisheval", nothing) | ||
217 | catch err | ||
218 | @debug "Could not send repl/finisheval notification." | ||
219 | end | ||
220 | end | ||
221 | |||
222 | if show_error && (res isa EvalError || res isa EvalErrorStack) | ||
223 | Base.display_error(stderr, res) | ||
224 | elseif show_result | ||
225 | if res isa EvalError || res isa EvalErrorStack | ||
226 | Base.display_error(stderr, res) | ||
227 | elseif res !== nothing && !ends_with_semicolon(source_code) | ||
228 | try | ||
229 | Base.invokelatest(display, res) | ||
230 | catch err | ||
231 | Base.display_error(stderr, err, catch_backtrace()) | ||
232 | end | ||
233 | end | ||
234 | else | ||
235 | try | ||
236 | if !ends_with_semicolon(source_code) && !(res isa EvalError || res isa EvalErrorStack) | ||
237 | with_no_default_display(() -> display(res); allow_inline = true) | ||
238 | end | ||
239 | catch err | ||
240 | if !(err isa MethodError && err.f === display) | ||
241 | printstyled(stderr, "Display Error: ", color = Base.error_color(), bold = true) | ||
242 | Base.display_error(stderr, err, catch_backtrace()) | ||
243 | end | ||
244 | end | ||
245 | end | ||
246 | |||
247 | if !(res isa EvalError || res isa EvalErrorStack) && ends_with_semicolon(source_code) | ||
248 | res = nothing | ||
249 | end | ||
250 | |||
251 | return safe_render(res) | ||
252 | end | ||
253 | end | ||
254 | |||
255 | 288 (100 %) |
288 (100 %)
samples spent calling
with_logger
return PROGRESS_ENABLED[] ? Logging.with_logger(f, VSCodeLogger()) : f()
|
|
256 | end | ||
257 | end | ||
258 | |||
259 | # don't inline this so we can find it in the stacktrace | ||
260 | 288 (100 %) |
576 (200 %)
samples spent in inlineeval
288 (50 %) (incl.) when called from inlineeval line 260 288 (50 %) (incl.) when called from #67 line 181
288 (100 %)
samples spent calling
#inlineeval#74
@noinline function inlineeval(m, code, code_line, code_column, file; softscope = false)
|
|
261 | code = string('\n'^code_line, ' '^code_column, code) | ||
262 | args = softscope && VERSION >= v"1.5" ? (REPL.softscope, m, code, file) : (m, code, file) | ||
263 | 288 (100 %) |
288 (100 %)
samples spent calling
invokelatest
return Base.invokelatest(include_string, args...)
|
|
264 | end | ||
265 | |||
266 | """ | ||
267 | safe_render(x) | ||
268 | |||
269 | Calls `render`, but catches errors in the display system. | ||
270 | """ | ||
271 | function safe_render(x) | ||
272 | try | ||
273 | return render(x) | ||
274 | catch err | ||
275 | out = render(EvalError(err, catch_backtrace())) | ||
276 | |||
277 | return ReplRunCodeRequestReturn( | ||
278 | string("Display Error: ", out.inline), | ||
279 | string("Display Error: ", out.all), | ||
280 | out.stackframe | ||
281 | ) | ||
282 | end | ||
283 | end | ||
284 | |||
285 | """ | ||
286 | render(x) | ||
287 | |||
288 | Produce a representation of `x` that can be displayed by a UI. | ||
289 | Must return a `ReplRunCodeRequestReturn` with the following fields: | ||
290 | - `inline::String`: Short one-line plain text representation of `x`. Typically limited to `INLINE_RESULT_LENGTH` characters. | ||
291 | - `all::String`: Plain text string (that may contain linebreaks and other signficant whitespace) to further describe `x`. | ||
292 | - `stackframe::Vector{Frame}`: Optional, should only be given on an error | ||
293 | """ | ||
294 | function render(x) | ||
295 | plain = sprintlimited(MIME"text/plain"(), x, limit = MAX_RESULT_LENGTH) | ||
296 | md = try | ||
297 | sprintlimited(MIME"text/markdown"(), x, limit = MAX_RESULT_LENGTH) | ||
298 | catch _ | ||
299 | codeblock(plain) | ||
300 | end | ||
301 | inline = strlimit(first(split(plain, "\n")), limit = INLINE_RESULT_LENGTH) | ||
302 | return ReplRunCodeRequestReturn(inline, md) | ||
303 | end | ||
304 | |||
305 | render(::Nothing) = ReplRunCodeRequestReturn("✓", codeblock("nothing")) | ||
306 | |||
307 | codeblock(s) = string("```\n", s, "\n```") | ||
308 | |||
309 | struct EvalError | ||
310 | err::Any | ||
311 | bt::Any | ||
312 | end | ||
313 | |||
314 | struct EvalErrorStack | ||
315 | stack::Any | ||
316 | end | ||
317 | |||
318 | sprint_error_unwrap(err) = sprint_error(unwrap_loaderror(err)) | ||
319 | |||
320 | unwrap_loaderror(err::LoadError) = err.error | ||
321 | unwrap_loaderror(err) = err | ||
322 | |||
323 | function sprint_error(err) | ||
324 | sprintlimited(err, [], func = Base.display_error, limit = MAX_RESULT_LENGTH) | ||
325 | end | ||
326 | |||
327 | function render(err::EvalError) | ||
328 | bt = crop_backtrace(err.bt) | ||
329 | |||
330 | errstr = sprint_error_unwrap(err.err) | ||
331 | inline = strlimit(first(split(errstr, "\n")), limit = INLINE_RESULT_LENGTH) | ||
332 | all = string('\n', codeblock(errstr), '\n', backtrace_string(bt)) | ||
333 | |||
334 | # handle duplicates e.g. from recursion | ||
335 | st = unique!(remove_kw_wrappers!(stacktrace(bt))) | ||
336 | # limit number of potential hovers shown in VSCode, just in case | ||
337 | st = st[1:min(end, 1000)] | ||
338 | |||
339 | stackframe = Frame.(st) | ||
340 | return ReplRunCodeRequestReturn(inline, all, stackframe) | ||
341 | end | ||
342 | |||
343 | function render(stack::EvalErrorStack) | ||
344 | inline = "" | ||
345 | all = "" | ||
346 | complete_bt = Union{Base.InterpreterIP,Ptr{Cvoid}}[] | ||
347 | for (i, (err, bt)) in enumerate(reverse(stack.stack)) | ||
348 | bt = crop_backtrace(bt) | ||
349 | append!(complete_bt, bt) | ||
350 | |||
351 | errstr = sprint_error_unwrap(err) | ||
352 | inline *= strlimit(first(split(errstr, "\n")), limit = INLINE_RESULT_LENGTH) | ||
353 | all *= string('\n', codeblock(errstr), '\n', backtrace_string(bt)) | ||
354 | end | ||
355 | |||
356 | # handle duplicates e.g. from recursion | ||
357 | st = unique!(remove_kw_wrappers!(stacktrace(complete_bt))) | ||
358 | # limit number of potential hovers shown in VSCode, just in case | ||
359 | st = st[1:min(end, 1000)] | ||
360 | |||
361 | stackframe = Frame.(st) | ||
362 | return ReplRunCodeRequestReturn(inline, all, stackframe) | ||
363 | end | ||
364 | |||
365 | function Base.display_error(io::IO, err::EvalError) | ||
366 | try | ||
367 | Base.invokelatest(display_repl_error, io, unwrap_loaderror(err.err), err.bt) | ||
368 | catch err | ||
369 | @error "Error trying to display an error." ex = (err, catch_backtrace()) | ||
370 | end | ||
371 | end | ||
372 | |||
373 | function Base.display_error(io::IO, err::EvalErrorStack) | ||
374 | try | ||
375 | Base.invokelatest(display_repl_error, io, err) | ||
376 | catch err | ||
377 | @error "Error trying to display an error." ex = (err, catch_backtrace()) | ||
378 | end | ||
379 | end | ||
380 | |||
381 | function remove_kw_wrappers!(st::StackTraces.StackTrace) | ||
382 | filter!(st) do frame | ||
383 | fname = string(frame.func) | ||
384 | return !(!startswith(fname, '#') && endswith(fname, "##kw")) | ||
385 | end | ||
386 | |||
387 | return st | ||
388 | end | ||
389 | |||
390 | function backtrace_string(bt) | ||
391 | limitflag = Ref(false) | ||
392 | |||
393 | iob = IOBuffer() | ||
394 | io = IOContext( | ||
395 | iob, | ||
396 | :stacktrace_types_limited => limitflag, | ||
397 | :displaysize => (120, 120) | ||
398 | ) | ||
399 | |||
400 | println(io, "Stacktrace:\n") | ||
401 | i = 1 | ||
402 | counter = 1 | ||
403 | stack = remove_kw_wrappers!(stacktrace(bt)) | ||
404 | |||
405 | while i <= length(stack) | ||
406 | if counter > 200 | ||
407 | println(io, "\n\n truncated") | ||
408 | break | ||
409 | end | ||
410 | |||
411 | frame, repeated = stack[i], 1 | ||
412 | while i < length(stack) && stack[i+1] == frame | ||
413 | i += 1 | ||
414 | repeated += 1 | ||
415 | end | ||
416 | |||
417 | file = string(frame.file) | ||
418 | full_file = fullpath(something(Base.find_source_file(file), file)) | ||
419 | cmd = vscode_cmd_uri("language-julia.openFile"; path = full_file, line = frame.line) | ||
420 | |||
421 | print(io, counter, ". `") | ||
422 | Base.StackTraces.show_spec_linfo(io, frame) | ||
423 | print(io, "` at [", basename(file), "](", cmd, " \"", file, "\")") | ||
424 | if repeated > 1 | ||
425 | print(io, " (repeats $repeated times)") | ||
426 | end | ||
427 | println(io, "\n") | ||
428 | i += 1 | ||
429 | counter += 1 | ||
430 | end | ||
431 | |||
432 | if limitflag[] | ||
433 | print(io, "Some type information was truncated. Use `show(err)` to see complete types.") | ||
434 | end | ||
435 | |||
436 | return String(take!(iob)) | ||
437 | end |