Remove unused variables (#18579)
[mono-project.git] / sdks / wasm / Mono.WebAssembly.DebuggerProxy / MonoProxy.cs
blobf7e4adf44ce83e91fe093f0a5d7e1008119746ca
1 using System;
2 using System.Linq;
3 using System.Threading.Tasks;
4 using Newtonsoft.Json.Linq;
6 using System.Net.WebSockets;
7 using System.Threading;
8 using System.IO;
9 using System.Text;
10 using System.Collections.Generic;
11 using System.Net;
13 namespace WsProxy {
15 internal class MonoCommands {
16 public const string GET_CALL_STACK = "MONO.mono_wasm_get_call_stack()";
17 public const string IS_RUNTIME_READY_VAR = "MONO.mono_wasm_runtime_is_ready";
18 public const string START_SINGLE_STEPPING = "MONO.mono_wasm_start_single_stepping({0})";
19 public const string GET_SCOPE_VARIABLES = "MONO.mono_wasm_get_variables({0}, [ {1} ])";
20 public const string SET_BREAK_POINT = "MONO.mono_wasm_set_breakpoint(\"{0}\", {1}, {2})";
21 public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})";
22 public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()";
23 public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()";
24 public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})";
25 public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})";
28 public enum MonoErrorCodes {
29 BpNotFound = 100000,
33 internal class MonoConstants {
34 public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
36 class Frame {
37 public Frame (MethodInfo method, SourceLocation location, int id)
39 this.Method = method;
40 this.Location = location;
41 this.Id = id;
44 public MethodInfo Method { get; private set; }
45 public SourceLocation Location { get; private set; }
46 public int Id { get; private set; }
50 class Breakpoint {
51 public SourceLocation Location { get; private set; }
52 public int LocalId { get; private set; }
53 public int RemoteId { get; set; }
54 public BreakPointState State { get; set; }
56 public Breakpoint (SourceLocation loc, int localId, BreakPointState state)
58 this.Location = loc;
59 this.LocalId = localId;
60 this.State = state;
64 enum BreakPointState {
65 Active,
66 Disabled,
67 Pending
70 enum StepKind {
71 Into,
72 Out,
73 Over
76 public class MonoProxy : WsProxy {
77 DebugStore store;
78 List<Breakpoint> breakpoints = new List<Breakpoint> ();
79 List<Frame> current_callstack;
80 bool runtime_ready;
81 int local_breakpoint_id;
82 int ctx_id;
83 JObject aux_ctx_data;
85 public MonoProxy () { }
87 protected override async Task<bool> AcceptEvent (string method, JObject args, CancellationToken token)
89 switch (method) {
90 case "Runtime.executionContextCreated": {
91 var ctx = args? ["context"];
92 var aux_data = ctx? ["auxData"] as JObject;
93 if (aux_data != null) {
94 var is_default = aux_data ["isDefault"]?.Value<bool> ();
95 if (is_default == true) {
96 var ctx_id = ctx ["id"].Value<int> ();
97 await OnDefaultContext (ctx_id, aux_data, token);
100 break;
102 case "Debugger.paused": {
103 //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
104 var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> ();
105 if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") {
106 await OnBreakPointHit (args, token);
107 return true;
109 if (top_func == MonoConstants.RUNTIME_IS_READY) {
110 await OnRuntimeReady (token);
111 return true;
113 break;
115 case "Debugger.scriptParsed":{
116 if (args?["url"]?.Value<string> ()?.StartsWith ("wasm://") == true) {
117 // Console.WriteLine ("ignoring wasm event");
118 return true;
120 break;
124 return false;
128 protected override async Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token)
130 switch (method) {
131 case "Debugger.getScriptSource": {
132 var script_id = args? ["scriptId"]?.Value<string> ();
133 if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) {
134 await OnGetScriptSource (id, script_id, token);
135 return true;
138 break;
140 case "Runtime.compileScript": {
141 var exp = args? ["expression"]?.Value<string> ();
142 if (exp.StartsWith ("//dotnet:", StringComparison.InvariantCultureIgnoreCase)) {
143 OnCompileDotnetScript (id, token);
144 return true;
146 break;
149 case "Debugger.getPossibleBreakpoints": {
150 var start = SourceLocation.Parse (args? ["start"] as JObject);
151 //FIXME support variant where restrictToFunction=true and end is omitted
152 var end = SourceLocation.Parse (args? ["end"] as JObject);
153 if (start != null && end != null)
154 return GetPossibleBreakpoints (id, start, end, token);
155 break;
158 case "Debugger.setBreakpointByUrl": {
159 Info ($"BP req {args}");
160 var bp_req = BreakPointRequest.Parse (args, store);
161 if (bp_req != null) {
162 await SetBreakPoint (id, bp_req, token);
163 return true;
165 break;
167 case "Debugger.removeBreakpoint": {
168 return await RemoveBreakpoint (id, args, token);
171 case "Debugger.resume": {
172 await OnResume (token);
173 break;
176 case "Debugger.stepInto": {
177 if (this.current_callstack != null) {
178 await Step (id, StepKind.Into, token);
179 return true;
181 break;
184 case "Debugger.stepOut": {
185 if (this.current_callstack != null) {
186 await Step (id, StepKind.Out, token);
187 return true;
189 break;
192 case "Debugger.stepOver": {
193 if (this.current_callstack != null) {
194 await Step (id, StepKind.Over, token);
195 return true;
197 break;
200 case "Runtime.getProperties": {
201 var objId = args? ["objectId"]?.Value<string> ();
202 if (objId.StartsWith ("dotnet:scope:", StringComparison.InvariantCulture)) {
203 await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token);
204 return true;
206 if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture))
208 if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture))
209 await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES);
210 if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture))
211 await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES);
212 return true;
214 break;
218 return false;
221 async Task OnRuntimeReady (CancellationToken token)
223 Info ("RUNTIME READY, PARTY TIME");
224 await RuntimeReady (token);
225 await SendCommand ("Debugger.resume", new JObject (), token);
226 SendEvent ("Mono.runtimeReady", new JObject (), token);
229 async Task OnBreakPointHit (JObject args, CancellationToken token)
231 //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
232 var o = JObject.FromObject (new {
233 expression = MonoCommands.GET_CALL_STACK,
234 objectGroup = "mono_debugger",
235 includeCommandLineAPI = false,
236 silent = false,
237 returnByValue = true
240 var orig_callframes = args? ["callFrames"]?.Values<JObject> ();
241 var res = await SendCommand ("Runtime.evaluate", o, token);
243 if (res.IsErr) {
244 //Give up and send the original call stack
245 SendEvent ("Debugger.paused", args, token);
246 return;
249 //step one, figure out where did we hit
250 var res_value = res.Value? ["result"]? ["value"];
251 if (res_value == null || res_value is JValue) {
252 //Give up and send the original call stack
253 SendEvent ("Debugger.paused", args, token);
254 return;
257 Debug ($"call stack (err is {res.Error} value is:\n{res.Value}");
258 var bp_id = res_value? ["breakpoint_id"]?.Value<int> ();
259 Debug ($"We just hit bp {bp_id}");
260 if (!bp_id.HasValue) {
261 //Give up and send the original call stack
262 SendEvent ("Debugger.paused", args, token);
263 return;
265 var bp = this.breakpoints.FirstOrDefault (b => b.RemoteId == bp_id.Value);
267 var src = bp == null ? null : store.GetFileById (bp.Location.Id);
269 var callFrames = new List<JObject> ();
270 foreach (var frame in orig_callframes) {
271 var function_name = frame ["functionName"]?.Value<string> ();
272 var url = frame ["url"]?.Value<string> ();
273 if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) {
274 var frames = new List<Frame> ();
275 int frame_id = 0;
276 var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
278 foreach (var mono_frame in the_mono_frames) {
279 var il_pos = mono_frame ["il_pos"].Value<int> ();
280 var method_token = mono_frame ["method_token"].Value<int> ();
281 var assembly_name = mono_frame ["assembly_name"].Value<string> ();
283 var asm = store.GetAssemblyByName (assembly_name);
284 if (asm == null) {
285 Info ($"Unable to find assembly: {assembly_name}");
286 continue;
289 var method = asm.GetMethodByToken (method_token);
291 if (method == null) {
292 Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
293 continue;
296 var location = method?.GetLocationByIl (il_pos);
298 // When hitting a breakpoint on the "IncrementCount" method in the standard
299 // Blazor project template, one of the stack frames is inside mscorlib.dll
300 // and we get location==null for it. It will trigger a NullReferenceException
301 // if we don't skip over that stack frame.
302 if (location == null) {
303 continue;
306 Info ($"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
307 Info ($"\tmethod {method.Name} location: {location}");
308 frames.Add (new Frame (method, location, frame_id));
310 callFrames.Add (JObject.FromObject (new {
311 functionName = method.Name,
312 callFrameId = $"dotnet:scope:{frame_id}",
313 functionLocation = method.StartLocation.ToJObject (),
315 location = location.ToJObject (),
317 url = store.ToUrl (location),
319 scopeChain = new [] {
320 new {
321 type = "local",
322 @object = new {
323 @type = "object",
324 className = "Object",
325 description = "Object",
326 objectId = $"dotnet:scope:{frame_id}",
328 name = method.Name,
329 startLocation = method.StartLocation.ToJObject (),
330 endLocation = method.EndLocation.ToJObject (),
332 }));
334 ++frame_id;
335 this.current_callstack = frames;
338 } else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture)
339 || url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) {
340 callFrames.Add (frame);
344 var bp_list = new string [bp == null ? 0 : 1];
345 if (bp != null)
346 bp_list [0] = $"dotnet:{bp.LocalId}";
348 o = JObject.FromObject (new {
349 callFrames = callFrames,
350 reason = "other", //other means breakpoint
351 hitBreakpoints = bp_list,
354 SendEvent ("Debugger.paused", o, token);
357 async Task OnDefaultContext (int ctx_id, JObject aux_data, CancellationToken token)
359 Debug ("Default context created, clearing state and sending events");
361 //reset all bps
362 foreach (var b in this.breakpoints){
363 b.State = BreakPointState.Pending;
365 this.runtime_ready = false;
367 var o = JObject.FromObject (new {
368 expression = MonoCommands.IS_RUNTIME_READY_VAR,
369 objectGroup = "mono_debugger",
370 includeCommandLineAPI = false,
371 silent = false,
372 returnByValue = true
374 this.ctx_id = ctx_id;
375 this.aux_ctx_data = aux_data;
377 Debug ("checking if the runtime is ready");
378 var res = await SendCommand ("Runtime.evaluate", o, token);
379 var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> ();
380 //Debug ($"\t{is_ready}");
381 if (is_ready.HasValue && is_ready.Value == true) {
382 Debug ("RUNTIME LOOK READY. GO TIME!");
383 await OnRuntimeReady (token);
388 async Task OnResume (CancellationToken token)
390 //discard frames
391 this.current_callstack = null;
392 await Task.CompletedTask;
395 async Task Step (int msg_id, StepKind kind, CancellationToken token)
398 var o = JObject.FromObject (new {
399 expression = string.Format (MonoCommands.START_SINGLE_STEPPING, (int)kind),
400 objectGroup = "mono_debugger",
401 includeCommandLineAPI = false,
402 silent = false,
403 returnByValue = true,
406 var res = await SendCommand ("Runtime.evaluate", o, token);
408 SendResponse (msg_id, Result.Ok (new JObject ()), token);
410 this.current_callstack = null;
412 await SendCommand ("Debugger.resume", new JObject (), token);
415 async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command)
417 var o = JObject.FromObject(new
419 expression = string.Format(command, object_id),
420 objectGroup = "mono_debugger",
421 includeCommandLineAPI = false,
422 silent = false,
423 returnByValue = true,
426 var res = await SendCommand("Runtime.evaluate", o, token);
428 //if we fail we just buble that to the IDE (and let it panic over it)
429 if (res.IsErr)
431 SendResponse(msg_id, res, token);
432 return;
435 try {
436 var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
437 var var_list = new List<JObject>();
439 // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
440 // results in a "Memory access out of bounds", causing 'values' to be null,
441 // so skip returning variable values in that case.
442 for (int i = 0; i < values.Length; i+=2)
444 string fieldName = (string)values[i]["name"];
445 if (fieldName.Contains("k__BackingField")){
446 fieldName = fieldName.Replace("k__BackingField", "");
447 fieldName = fieldName.Replace("<", "");
448 fieldName = fieldName.Replace(">", "");
450 var value = values [i + 1]? ["value"];
451 if (((string)value ["description"]) == null)
452 value ["description"] = value ["value"]?.ToString ();
454 var_list.Add(JObject.FromObject(new {
455 name = fieldName,
456 value
457 }));
460 o = JObject.FromObject(new
462 result = var_list
464 } catch {
465 Debug ($"failed to parse {res.Value}");
467 SendResponse(msg_id, Result.Ok(o), token);
471 async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token)
473 var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id);
474 var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
477 var var_ids = string.Join (",", vars.Select (v => v.Index));
479 var o = JObject.FromObject (new {
480 expression = string.Format (MonoCommands.GET_SCOPE_VARIABLES, scope.Id, var_ids),
481 objectGroup = "mono_debugger",
482 includeCommandLineAPI = false,
483 silent = false,
484 returnByValue = true,
487 var res = await SendCommand ("Runtime.evaluate", o, token);
489 //if we fail we just buble that to the IDE (and let it panic over it)
490 if (res.IsErr) {
491 SendResponse (msg_id, res, token);
492 return;
495 try {
496 var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
498 var var_list = new List<JObject> ();
499 int i = 0;
500 // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
501 // results in a "Memory access out of bounds", causing 'values' to be null,
502 // so skip returning variable values in that case.
503 while (values != null && i < vars.Length && i < values.Length) {
504 var value = values [i] ["value"];
505 if (((string)value ["description"]) == null)
506 value ["description"] = value ["value"]?.ToString ();
508 var_list.Add (JObject.FromObject (new {
509 name = vars [i].Name,
510 value
511 }));
512 i++;
514 //Async methods are special in the way that local variables can be lifted to generated class fields
515 //value of "this" comes here either
516 while (i < values.Length) {
517 String name = values [i] ["name"].ToString ();
519 if (name.IndexOf (">", StringComparison.Ordinal) > 0)
520 name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
522 var value = values [i + 1] ["value"];
523 if (((string)value ["description"]) == null)
524 value ["description"] = value ["value"]?.ToString ();
526 var_list.Add (JObject.FromObject (new {
527 name,
528 value
529 }));
530 i = i + 2;
532 o = JObject.FromObject (new {
533 result = var_list
535 SendResponse (msg_id, Result.Ok (o), token);
537 catch {
538 SendResponse (msg_id, res, token);
542 async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token)
544 var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
545 var method_token = bp.Location.CliLocation.Method.Token;
546 var il_offset = bp.Location.CliLocation.Offset;
548 var o = JObject.FromObject (new {
549 expression = string.Format (MonoCommands.SET_BREAK_POINT, asm_name, method_token, il_offset),
550 objectGroup = "mono_debugger",
551 includeCommandLineAPI = false,
552 silent = false,
553 returnByValue = true,
556 var res = await SendCommand ("Runtime.evaluate", o, token);
557 var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
559 if (ret_code.HasValue) {
560 bp.RemoteId = ret_code.Value;
561 bp.State = BreakPointState.Active;
562 //Debug ($"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
565 return res;
568 async Task RuntimeReady (CancellationToken token)
571 var o = JObject.FromObject (new {
572 expression = MonoCommands.GET_LOADED_FILES,
573 objectGroup = "mono_debugger",
574 includeCommandLineAPI = false,
575 silent = false,
576 returnByValue = true,
578 var loaded_pdbs = await SendCommand ("Runtime.evaluate", o, token);
579 var the_value = loaded_pdbs.Value? ["result"]? ["value"];
580 var the_pdbs = the_value?.ToObject<string[]> ();
581 this.store = new DebugStore (the_pdbs);
583 foreach (var s in store.AllSources ()) {
584 var ok = JObject.FromObject (new {
585 scriptId = s.SourceId.ToString (),
586 url = s.Url,
587 executionContextId = this.ctx_id,
588 hash = s.DocHashCode,
589 executionContextAuxData = this.aux_ctx_data,
590 dotNetUrl = s.DotNetUrl
592 //Debug ($"\tsending {s.Url}");
593 SendEvent ("Debugger.scriptParsed", ok, token);
596 o = JObject.FromObject (new {
597 expression = MonoCommands.CLEAR_ALL_BREAKPOINTS,
598 objectGroup = "mono_debugger",
599 includeCommandLineAPI = false,
600 silent = false,
601 returnByValue = true,
604 var clear_result = await SendCommand ("Runtime.evaluate", o, token);
605 if (clear_result.IsErr) {
606 Debug ($"Failed to clear breakpoints due to {clear_result}");
610 runtime_ready = true;
612 foreach (var bp in breakpoints) {
613 if (bp.State != BreakPointState.Pending)
614 continue;
615 var res = await EnableBreakPoint (bp, token);
616 var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
618 //if we fail we just buble that to the IDE (and let it panic over it)
619 if (!ret_code.HasValue) {
620 //FIXME figure out how to inform the IDE of that.
621 Info ($"FAILED TO ENABLE BP {bp.LocalId}");
622 bp.State = BreakPointState.Disabled;
627 async Task<bool> RemoveBreakpoint(int msg_id, JObject args, CancellationToken token) {
628 var bpid = args? ["breakpointId"]?.Value<string> ();
629 if (bpid?.StartsWith ("dotnet:") != true)
630 return false;
632 var the_id = int.Parse (bpid.Substring ("dotnet:".Length));
634 var bp = breakpoints.FirstOrDefault (b => b.LocalId == the_id);
635 if (bp == null) {
636 Info ($"Could not find dotnet bp with id {the_id}");
637 return false;
640 breakpoints.Remove (bp);
641 //FIXME verify result (and log?)
642 var res = await RemoveBreakPoint (bp, token);
644 return true;
648 async Task<Result> RemoveBreakPoint (Breakpoint bp, CancellationToken token)
650 var o = JObject.FromObject (new {
651 expression = string.Format (MonoCommands.REMOVE_BREAK_POINT, bp.RemoteId),
652 objectGroup = "mono_debugger",
653 includeCommandLineAPI = false,
654 silent = false,
655 returnByValue = true,
658 var res = await SendCommand ("Runtime.evaluate", o, token);
659 var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
661 if (ret_code.HasValue) {
662 bp.RemoteId = -1;
663 bp.State = BreakPointState.Disabled;
666 return res;
669 async Task SetBreakPoint (int msg_id, BreakPointRequest req, CancellationToken token)
671 var bp_loc = store.FindBestBreakpoint (req);
672 Info ($"BP request for '{req}' runtime ready {runtime_ready} location '{bp_loc}'");
673 if (bp_loc == null) {
675 Info ($"Could not resolve breakpoint request: {req}");
676 SendResponse (msg_id, Result.Err(JObject.FromObject (new {
677 code = (int)MonoErrorCodes.BpNotFound,
678 message = $"C# Breakpoint at {req} not found."
679 })), token);
680 return;
683 Breakpoint bp = null;
684 if (!runtime_ready) {
685 bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Pending);
686 } else {
687 bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Disabled);
689 var res = await EnableBreakPoint (bp, token);
690 var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
692 //if we fail we just buble that to the IDE (and let it panic over it)
693 if (!ret_code.HasValue) {
694 SendResponse (msg_id, res, token);
695 return;
699 var locations = new List<JObject> ();
701 locations.Add (JObject.FromObject (new {
702 scriptId = bp_loc.Id.ToString (),
703 lineNumber = bp_loc.Line,
704 columnNumber = bp_loc.Column
705 }));
707 breakpoints.Add (bp);
709 var ok = JObject.FromObject (new {
710 breakpointId = $"dotnet:{bp.LocalId}",
711 locations = locations,
714 SendResponse (msg_id, Result.Ok (ok), token);
717 bool GetPossibleBreakpoints (int msg_id, SourceLocation start, SourceLocation end, CancellationToken token)
719 var bps = store.FindPossibleBreakpoints (start, end);
720 if (bps == null)
721 return false;
723 var loc = new List<JObject> ();
724 foreach (var b in bps) {
725 loc.Add (b.ToJObject ());
728 var o = JObject.FromObject (new {
729 locations = loc
732 SendResponse (msg_id, Result.Ok (o), token);
734 return true;
737 void OnCompileDotnetScript (int msg_id, CancellationToken token)
739 var o = JObject.FromObject (new { });
741 SendResponse (msg_id, Result.Ok (o), token);
745 async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token)
747 var id = new SourceId (script_id);
748 var src_file = store.GetFileById (id);
750 var res = new StringWriter ();
751 //res.WriteLine ($"//{id}");
753 try {
754 var uri = new Uri (src_file.Url);
755 if (uri.IsFile && File.Exists(uri.LocalPath)) {
756 using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) {
757 await res.WriteAsync (await f.ReadToEndAsync ());
760 var o = JObject.FromObject (new {
761 scriptSource = res.ToString ()
764 SendResponse (msg_id, Result.Ok (o), token);
765 } else if(src_file.SourceLinkUri != null) {
766 var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri);
767 await res.WriteAsync (doc);
769 var o = JObject.FromObject (new {
770 scriptSource = res.ToString ()
773 SendResponse (msg_id, Result.Ok (o), token);
774 } else {
775 var o = JObject.FromObject (new {
776 scriptSource = $"// Unable to find document {src_file.SourceUri}"
779 SendResponse (msg_id, Result.Ok (o), token);
781 } catch (Exception e) {
782 var o = JObject.FromObject (new {
783 scriptSource = $"// Unable to read document ({e.Message})\n" +
784 $"Local path: {src_file?.SourceUri}\n" +
785 $"SourceLink path: {src_file?.SourceLinkUri}\n"
788 SendResponse (msg_id, Result.Ok (o), token);