Skip to content

Commit 419a6e9

Browse files
Cleanup
1 parent d3411b0 commit 419a6e9

File tree

5 files changed

+125
-178
lines changed

5 files changed

+125
-178
lines changed
Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11

2-
"use strict";
2+
/*
3+
This file contains an implementation of vscode.Debugadapter.
4+
5+
DAP messages are received via `DebugAdapter.handleMessage` and sent via
6+
`onDidSendMessage`.
7+
8+
Most messages are simply passed to the R pacakge by calling
9+
`this.debugRuntime.dispatchRequest()`, only some requests are modified/handled
10+
in `this.dispatchRequest()`.
11+
*/
312

413
import { DebugRuntime } from './debugRuntime';
5-
import { ProtocolServer } from './protocol';
614
import { DebugProtocol } from 'vscode-debugprotocol';
7-
import { InitializeRequest, ResponseWithBody, InitializeRequestArguments, ContinueRequest } from './debugProtocolModifications';
15+
import { InitializeRequest } from './debugProtocolModifications';
816
import { config, getVSCodePackageVersion } from './utils';
917

1018
import * as vscode from 'vscode';
@@ -28,25 +36,17 @@ function logMessage(message: DebugProtocol.ProtocolMessage){
2836
return ret;
2937
}
3038

31-
export class DebugSession extends ProtocolServer {
3239

33-
// we don't support multiple threads, so we can use a hardcoded ID for the default thread
34-
private THREAD_ID = 1;
35-
36-
// a runtime (or debugger)
37-
private runtime: DebugRuntime;
40+
export class DebugAdapter implements vscode.DebugAdapter {
3841

42+
// properties
43+
private sendMessage = new vscode.EventEmitter<DebugProtocol.ProtocolMessage>(); // used by onDidSendMessage
44+
private sequence: number = 0; // seq of messages sent to VS Code
45+
private THREAD_ID = 1; // dummy value
46+
private runtime: DebugRuntime; // actually handles requests etc. that are not forwarded
3947
private disconnectTimeout: number = config().get<number>('timeouts.startup', 1000);
4048

41-
42-
sendProtocolMessage(message: DebugProtocol.ProtocolMessage): void {
43-
logger.info(logMessage(message), message);
44-
super.sendProtocolMessage(message);
45-
}
46-
4749
constructor() {
48-
super();
49-
5050
// construct R runtime
5151
this.runtime = new DebugRuntime();
5252

@@ -56,12 +56,26 @@ export class DebugSession extends ProtocolServer {
5656
});
5757
}
5858

59-
// static run(debugSession: typeof DebugSession): void {
60-
// const session = new debugSession();
61-
// session.start(process.stdin, process.stdout);
62-
// }
59+
// dummy, required by vscode.Disposable (?)
60+
public dispose(): void {};
61+
62+
// used to send messages from R to VS Code
63+
readonly onDidSendMessage: vscode.Event<DebugProtocol.ProtocolMessage> = this.sendMessage.event;
6364

64-
protected dispatchRequest(request: DebugProtocol.Request) {
65+
// used to send messages from VS Code to R
66+
public handleMessage(msg: DebugProtocol.ProtocolMessage): void {
67+
if(msg.type === 'request') {
68+
this.dispatchRequest(<DebugProtocol.Request>msg);
69+
}
70+
}
71+
72+
protected sendProtocolMessage(message: DebugProtocol.ProtocolMessage): void {
73+
logger.info(logMessage(message), message);
74+
message.seq = this.sequence++;
75+
this.sendMessage.fire(message);
76+
}
77+
78+
protected dispatchRequest(request: DebugProtocol.Request): void {
6579
// prepare response
6680
const response: DebugProtocol.Response = {
6781
command: request.command,
@@ -90,15 +104,18 @@ export class DebugSession extends ProtocolServer {
90104
case 'evaluate':
91105
const matches = /^### ?[sS][tT][dD][iI][nN]\s*(.*)$/s.exec(request.arguments.expression);
92106
if(matches){
107+
// send directly to stdin, don't send request
93108
const toStdin = matches[1];
94109
logger.debug('user to stdin:\n' + toStdin);
95110
this.runtime.rSession.writeToStdin(toStdin);
96111
} else{
112+
// dispatch normally
97113
dispatchToR = true;
98114
sendResponse = false;
99115
}
100116
break;
101117
case 'disconnect':
118+
// kill R process after timeout, in case it doesn't quit successfully
102119
setTimeout(()=>{
103120
console.log('killing R...');
104121
this.runtime.killR();
@@ -107,7 +124,8 @@ export class DebugSession extends ProtocolServer {
107124
sendResponse = false;
108125
break;
109126
case 'continue':
110-
// this.runtime.continue(<ContinueRequest>request);
127+
// pass info about the currently open text editor
128+
// can be used to start .vsc.debugSource(), when called from global workspace
111129
const doc = vscode.window.activeTextEditor.document;
112130
if(doc.uri.scheme === 'file'){
113131
const filename = doc.fileName;
@@ -125,18 +143,25 @@ export class DebugSession extends ProtocolServer {
125143
// request not handled here -> send to R
126144
dispatchToR = true;
127145
sendResponse = false;
146+
// end cases
128147
}
129-
if(dispatchToR){
130-
this.runtime.dispatchRequest(request);
131-
} else{
132-
logger.info("request " + request.seq + " (handled in VS Code): " + request.command, request);
133-
}
134-
if(sendResponse){
135-
this.sendProtocolMessage(response);
136-
}
148+
} catch (e) {
149+
logger.error("Error while handling request " + request.seq + ": " + request.command);
150+
response.success = false;
151+
dispatchToR = false;
152+
sendResponse = true;
153+
}
154+
155+
// dispatch to R if not (completely) handled here
156+
if(dispatchToR){
157+
this.runtime.dispatchRequest(request);
158+
} else{
159+
logger.info("request " + request.seq + " (handled in VS Code): " + request.command, request);
137160
}
138-
catch (e) {
139-
logger.error("Error while handling request " + request.seq + ": " + request.command);
161+
162+
// send response if (completely) handled here
163+
if(sendResponse){
164+
this.sendProtocolMessage(response);
140165
}
141166
}
142167
}

src/debugRuntime.ts

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
/*---------------------------------------------------------
2-
* Copyright (C) Microsoft Corporation. All rights reserved.
3-
*--------------------------------------------------------*/
41

52
import { EventEmitter } from 'events';
63
import * as vscode from 'vscode';
@@ -80,22 +77,18 @@ export class DebugRuntime extends EventEmitter {
8077
public async initializeRequest(response: DebugProtocol.InitializeResponse, args: MDebugProtocol.InitializeRequestArguments, request: MDebugProtocol.InitializeRequest) {
8178
// This function initializes a debug session with the following steps:
8279
// 1. Handle arguments
83-
// 2. Launch a child process
80+
// 2. Launch a child process running R
8481
// 3. Check that the child process started
8582
// 4. Load the R package vscDebugger
8683
// 5. Check that the R package is present and has a correct version
8784

8885

8986
//// (1) Handle arguments
90-
if(args.rStrings){
91-
const rs1 = args.rStrings;
92-
const rs0 = this.rStrings;
93-
this.rStrings.prompt = rs1.prompt || rs0.prompt;
94-
this.rStrings.continue = rs1.continue || rs0.continue;
95-
this.rStrings.startup = rs1.startup || rs0.startup;
96-
this.rStrings.libraryNotFound = rs1.libraryNotFound || rs0.libraryNotFound;
97-
this.rStrings.packageName = rs1.packageName || rs0.packageName;
98-
}
87+
// update rStrings
88+
this.rStrings = {
89+
...this.rStrings,
90+
...args.rStrings
91+
};
9992
args.rStrings = this.rStrings;
10093

10194
// read settings from vsc-settings
@@ -106,15 +99,14 @@ export class DebugRuntime extends EventEmitter {
10699
this.outputModes["sinkSocket"] = config().get<OutputMode>('printSinkSocket', 'filtered');
107100

108101
// start R in child process
109-
const rStartupArguments: MDebugProtocol.RStartupArguments = await getRStartupArguments();
102+
const rStartupArguments = await getRStartupArguments();
110103
const openFolders = vscode.workspace.workspaceFolders;
111104
if(openFolders){
112105
rStartupArguments.cwd = openFolders[0].uri.fsPath;
113106
}
114107

115108
if(!rStartupArguments.path){
116109
const message = 'No R path was found in the settings/path/registry.\n(Can be changed in setting rdebugger.rterm.XXX)';
117-
// const message = 'R path not working:\n' + rPath + '\n';
118110
await this.abortInitializeRequest(response, message);
119111
return false;
120112
}
@@ -124,7 +116,6 @@ export class DebugRuntime extends EventEmitter {
124116
this.startOutputGroup('Starting R session...', true);
125117
this.writeOutput('R Startup:\n' + JSON.stringify(rStartupArguments, undefined, 2));
126118

127-
128119
//// (2) Launch child process
129120
const tmpHandleLine: LineHandler = (line: string, from: DataSource, isFullLine: boolean) => {
130121
return this.handleLine(line, from, isFullLine);
@@ -143,7 +134,7 @@ export class DebugRuntime extends EventEmitter {
143134

144135
// read ports that were assigned to the child process and add to initialize args
145136
if(this.rSession.jsonPort <= 0 || this.rSession.sinkPort <= 0){
146-
const message = 'Failed to connect to R process using ports!';
137+
const message = 'Failed to listen on port!';
147138
await this.abortInitializeRequest(response, message);
148139
return false;
149140
} else{
@@ -160,7 +151,7 @@ export class DebugRuntime extends EventEmitter {
160151
const escapedStartupString = escapeStringForR(this.rStrings.startup + '\n');
161152
const startupCmd = `base::cat(${escapedStartupString})`;
162153
this.rSession.writeToStdin(startupCmd);
163-
// this.rSession.callFunction('cat', this.rStrings.startup, '\n', true, 'base');
154+
164155
// `this.rSessionStartup` is notified when the output of the above `cat()` call is received
165156
await this.rSessionStartup.wait(this.startupTimeout);
166157
if (this.rSessionReady) {
@@ -220,7 +211,7 @@ export class DebugRuntime extends EventEmitter {
220211
return true;
221212
}
222213

223-
private async abortInitializeRequest(response: DebugProtocol.InitializeResponse, message: string, endOutputGroup: boolean = true){
214+
protected async abortInitializeRequest(response: DebugProtocol.InitializeResponse, message: string, endOutputGroup: boolean = true){
224215
// used to abort the debug session and return an unsuccessful InitializeResponse
225216
logger.error(message);
226217
if(endOutputGroup){
@@ -241,7 +232,7 @@ export class DebugRuntime extends EventEmitter {
241232
// Output-handlers: (for output of the R process to stdout/stderr)
242233
//////////
243234

244-
public handleLine(line: string, from: DataSource, isFullLine: boolean): string {
235+
protected handleLine(line: string, from: DataSource, isFullLine: boolean): string {
245236
// handle output from the R process line by line
246237
// is called by rSession.handleData()
247238

@@ -359,7 +350,7 @@ export class DebugRuntime extends EventEmitter {
359350
return line;
360351
}
361352

362-
private async handlePrompt(which: "browser"|"topLevel", text?: string){
353+
protected async handlePrompt(which: "browser"|"topLevel", text?: string){
363354
logger.debug("matches prompt: " + which);
364355

365356
// wait for timeout to give json socket time to catch up
@@ -391,7 +382,7 @@ export class DebugRuntime extends EventEmitter {
391382
}
392383
}
393384

394-
private sendShowingPromptRequest(which: "browser"|"topLevel", text?: string){
385+
protected sendShowingPromptRequest(which: "browser"|"topLevel", text?: string){
395386
const request: MDebugProtocol.ShowingPromptRequest = {
396387
command: "custom",
397388
arguments: {
@@ -405,7 +396,7 @@ export class DebugRuntime extends EventEmitter {
405396
this.dispatchRequest(request);
406397
}
407398

408-
public handleJsonString(json: string, from?: DataSource, isFullLine: boolean = true){
399+
protected handleJsonString(json: string, from?: DataSource, isFullLine: boolean = true){
409400
if(!isFullLine){
410401
return json;
411402
} else{
@@ -415,7 +406,7 @@ export class DebugRuntime extends EventEmitter {
415406
}
416407
}
417408

418-
public handleJson(json: any){
409+
protected handleJson(json: any){
419410
if(json.type === "response"){
420411
if(json.command === "initialize"){
421412
this.rPackageFound = true;
@@ -450,7 +441,12 @@ export class DebugRuntime extends EventEmitter {
450441
}
451442
}
452443

453-
public handleWriteToStdinEvent(args: MDebugProtocol.WriteToStdinBody){
444+
// send DAP message to the debugSession
445+
protected sendProtocolMessage(message: DebugProtocol.ProtocolMessage){
446+
this.emit("protocolMessage", message);
447+
}
448+
449+
protected handleWriteToStdinEvent(args: MDebugProtocol.WriteToStdinBody){
454450
let count: number = 0;
455451
if(args.count !== 0){
456452
count = args.count || 1;
@@ -491,7 +487,6 @@ export class DebugRuntime extends EventEmitter {
491487
public writeToStdin(text: string){
492488
if(text){
493489
logger.debug("Writing to stdin: ", text);
494-
// this.rSession.runCommand(text, [], true);
495490
this.rSession.writeToStdin(text);
496491
return true;
497492
} else{
@@ -519,10 +514,6 @@ export class DebugRuntime extends EventEmitter {
519514
}
520515
}
521516

522-
// send message to the debugSession
523-
private sendProtocolMessage(message: DebugProtocol.ProtocolMessage){
524-
this.emit("protocolMessage", message);
525-
}
526517

527518

528519
//////////////////////////////////////////////
@@ -536,6 +527,7 @@ export class DebugRuntime extends EventEmitter {
536527
data: object = {}
537528
): boolean {
538529
// writes output to the debug console (of the vsc instance runnning the R code)
530+
// used during start up to print info about errors/progress
539531
if(text.slice(-1) !== '\n' && addNewline){
540532
text = text + '\n';
541533
}
@@ -565,7 +557,7 @@ export class DebugRuntime extends EventEmitter {
565557
this.sendProtocolMessage(event);
566558
return true; // output event was sent
567559
}
568-
private startOutputGroup(text: string = "", collapsed: boolean = false, addNewline = false, toStderr = false, line = 1){
560+
public startOutputGroup(text: string = "", collapsed: boolean = false, addNewline = false, toStderr = false, line = 1){
569561
const group = (collapsed ? 'startCollapsed' : 'start');
570562
this.writeOutput(text, addNewline, (toStderr ? 'stderr' : 'stdout'), line, group);
571563
}

src/extension.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import * as vscode from 'vscode';
33
import { WorkspaceFolder, ProviderResult, CancellationToken, DebugConfigurationProviderTriggerKind } from 'vscode';
4-
import { DebugSession } from './debugSession';
4+
import { DebugAdapter } from './debugAdapter';
55
import {
66
DebugMode, FunctionDebugConfiguration,
77
FileDebugConfiguration, WorkspaceDebugConfiguration,
@@ -19,8 +19,6 @@ export async function activate(context: vscode.ExtensionContext) {
1919
terminalHandler = new TerminalHandler();
2020
const port = await terminalHandler.portPromise;
2121

22-
console.log('activate');
23-
2422
// register configuration resolver
2523
const resolver = new DebugConfigurationResolver(port);
2624
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('R-Debugger', resolver));
@@ -57,6 +55,22 @@ export function deactivate() {
5755
}
5856
}
5957

58+
class DebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
59+
createDebugAdapterDescriptor(session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
60+
const config = session.configuration;
61+
if(config.request === 'launch'){
62+
return new vscode.DebugAdapterInlineImplementation(new DebugAdapter());
63+
} else if(config.request === 'attach'){
64+
const port: number = config.port || 18721;
65+
const host: string = config.host || 'localhost';
66+
return new vscode.DebugAdapterServer(port, host);
67+
} else{
68+
throw new Error('Invalid entry "request" in debug config. Valid entries are "launch" and "attach"');
69+
}
70+
}
71+
}
72+
73+
6074
class InitialDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
6175
provideDebugConfigurations(folder: WorkspaceFolder | undefined): ProviderResult<StrictDebugConfiguration[]>{
6276
return [
@@ -232,19 +246,3 @@ class DebugConfigurationResolver implements vscode.DebugConfigurationProvider {
232246
return strictConfig;
233247
}
234248
}
235-
236-
class DebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
237-
createDebugAdapterDescriptor(session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
238-
const config = session.configuration;
239-
if(config.request === 'launch'){
240-
return new vscode.DebugAdapterInlineImplementation(new DebugSession());
241-
} else if(config.request === 'attach'){
242-
const port: number = config.port || 18721;
243-
const host: string = config.host || 'localhost';
244-
return new vscode.DebugAdapterServer(port, host);
245-
} else{
246-
throw new Error('Invalid entry "request" in debug config. Valid entries are "launch" and "attach"');
247-
}
248-
}
249-
}
250-

0 commit comments

Comments
 (0)