Skip to content

Commit

Permalink
Implement basic support for running in terminal to support reading st…
Browse files Browse the repository at this point in the history
…din.

Fixes #99.
  • Loading branch information
DanTup committed Nov 20, 2019
1 parent d060db1 commit 06f3bf1
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
19 changes: 17 additions & 2 deletions src/debug/dart_debug_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class DartDebugSession extends DebugSession {
private logStream?: fs.WriteStream;
public debugSdkLibraries = false;
public debugExternalLibraries = false;
public sendOutputAsCustomEvent = false;
public showDartDeveloperLogs = true;
public useFlutterStructuredErrors = false;
public evaluateGettersInDebugViews = false;
Expand Down Expand Up @@ -135,6 +136,7 @@ export class DartDebugSession extends DebugSession {
this.showDartDeveloperLogs = args.showDartDeveloperLogs;
this.useFlutterStructuredErrors = args.useFlutterStructuredErrors;
this.evaluateGettersInDebugViews = args.evaluateGettersInDebugViews;
this.sendOutputAsCustomEvent = args.console === "terminal";
this.debuggerHandlesPathsEverywhereForBreakpoints = args.debuggerHandlesPathsEverywhereForBreakpoints;
this.logFile = args.observatoryLogFile;
this.maxLogLineLength = args.maxLogLineLength;
Expand Down Expand Up @@ -1200,6 +1202,15 @@ export class DartDebugSession extends DebugSession {
this.errorResponse(response, e && e.message);
}
break;
case "dart.userInput":
if (this.childProcess && !this.childProcess.killed && !this.processExited) {
try {
this.childProcess.stdin.write(args.input);
} catch (e) {
this.logger.error(`Failed to write to process stdin: ${e}`);
}
}
break;
// Flutter requests that may be sent during test runs or other places
// that we don't currently support. TODO: Fix this by moving all the
// service extension stuff out of Flutter to here, and making it not
Expand Down Expand Up @@ -1801,8 +1812,8 @@ export class DartDebugSession extends DebugSession {

const lastNewLine = this.logBuffer[category].lastIndexOf("\n");
if (lastNewLine !== -1) {
const processString = this.logBuffer[category].substr(0, lastNewLine);
this.logBuffer[category] = this.logBuffer[category].substr(lastNewLine);
const processString = this.logBuffer[category].substr(0, lastNewLine + 1);
this.logBuffer[category] = this.logBuffer[category].substr(lastNewLine + 1);
this.logToUser(processString, category);
}
}
Expand All @@ -1811,6 +1822,10 @@ export class DartDebugSession extends DebugSession {
// Logs a message back to the editor. Does not add its own newlines, you must
// provide them!
protected logToUser(message: string, category?: string, colorText = (s: string) => s) {
if (this.sendOutputAsCustomEvent) {
this.sendEvent(new Event("dart.output", { message, category }));
return;
}
// Extract stack frames from the message so we can do nicer formatting of them.
const frame = this.getStackFrameData(message) || this.getWebStackFrameData(message) || this.getMessageWithUriData(message);

Expand Down
23 changes: 21 additions & 2 deletions src/extension/commands/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { FlutterServiceExtensionArgs, FlutterVmServiceExtensions, timeDilationNo
import { DebuggerType } from "../providers/debug_config_provider";
import { PubGlobal } from "../pub/global";
import { DevToolsManager } from "../sdk/dev_tools";
import { DartDebugSessionInformation } from "../utils/vscode/debug";
import { DartDebugSessionInformation, DartDebugSessionPseudoterminal } from "../utils/vscode/debug";
import { envUtils } from "../utils/vscode/editor";

export const debugSessions: DartDebugSessionInformation[] = [];
Expand Down Expand Up @@ -249,7 +249,15 @@ export class DebugCommands {

public handleDebugSessionStart(s: vs.DebugSession): void {
if (s.type === "dart") {
const session = new DartDebugSessionInformation(s, s.configuration ? DebuggerType[s.configuration.debuggerType] : "<unknown>");
const debuggerType = s.configuration ? DebuggerType[s.configuration.debuggerType] : "<unknown>";
const consoleType: "debugConsole" | "terminal" = s.configuration.console;
let terminal: DartDebugSessionPseudoterminal | undefined;
if (consoleType === "terminal") {
const terminalName = s.name || debuggerType;
terminal = new DartDebugSessionPseudoterminal(terminalName);
terminal.userInput((input) => s.customRequest("dart.userInput", { input }));
}
const session = new DartDebugSessionInformation(s, debuggerType, terminal);
// If we're the first fresh debug session, reset all settings to default.
// Subsequent launches will inherit the "current" values.
if (debugSessions.length === 0)
Expand Down Expand Up @@ -305,9 +313,14 @@ export class DebugCommands {
debugSessions.splice(sessionIndex, 1);

this.clearProgressIndicators(session);

if (session.terminal)
session.terminal.end();

this.debugMetrics.hide();
const debugSessionEnd = new Date();
this.analytics.logDebugSessionDuration(session.debuggerType, debugSessionEnd.getTime() - session.sessionStart.getTime());

// If this was the last session terminating, then remove all the flags for which service extensions are supported.
// Really we should track these per-session, but the changes of them being different given we only support one
// SDK at a time are practically zero.
Expand Down Expand Up @@ -383,6 +396,12 @@ export class DebugCommands {
);
} else if (e.event === "dart.launched") {
this.clearProgressIndicators(session);
} else if (e.event === "dart.output") {
if (session.terminal) {
session.terminal.addOutput(e.body.message, e.body.category);
} else {
this.logger.warn(`Got output event for session without pseudoterminal: ${e.body.message}`);
}
} else if (e.event === "dart.progress") {
if (e.body.message) {
if (session.launchProgressReporter) {
Expand Down
66 changes: 65 additions & 1 deletion src/extension/utils/vscode/debug.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vs from "vscode";
import { PromiseCompleter } from "../../../shared/utils";
import { red, yellow } from "../../../shared/utils/colors";

export class DartDebugSessionInformation {
public observatoryUri?: string;
Expand All @@ -12,5 +13,68 @@ export class DartDebugSessionInformation {
public progressReporter?: vs.Progress<{ message?: string; increment?: number; }>;
public progressID?: string;
public readonly sessionStart: Date = new Date();
constructor(public readonly session: vs.DebugSession, public readonly debuggerType: string) { }
constructor(public readonly session: vs.DebugSession, public readonly debuggerType: string, public readonly terminal: DartDebugSessionPseudoterminal | undefined) { }
}

const spawnedTerminalsByName: { [key: string]: vs.Terminal } = {};
export class DartDebugSessionPseudoterminal {
private readonly userInputEmitter = new vs.EventEmitter<string>();
public readonly userInput = this.userInputEmitter.event;
private readonly emitter = new vs.EventEmitter<string>();
private readonly terminal: vs.Terminal;
private readonly pseudoterminal: vs.Pseudoterminal;
private isRunning = true;

constructor(public readonly terminalName: string) {
this.pseudoterminal = {
close: () => { }, // TODO: End debug session!
handleInput: (data) => {
if (!this.isRunning)
return;

data = data === "\r" ? "\r\n" : data;
this.addOutput(data, "userInput");
this.userInputEmitter.fire(data);
},
onDidWrite: this.emitter.event,
// tslint:disable-next-line: no-empty
open: () => { },

};
// Close any existing terminal with the same name to ensure we're not creating
// new ones each time.
if (spawnedTerminalsByName[terminalName])
spawnedTerminalsByName[terminalName].dispose();
this.terminal = vs.window.createTerminal({ name: terminalName, pty: this.pseudoterminal });
spawnedTerminalsByName[terminalName] = this.terminal;
this.terminal.show();
}

public end() {
// Used to stop echoing user input back once the process has terminated.
this.isRunning = false;
}

public addOutput(output: string, category: string) {
if (!output)
return;

// If we don't send \r's then VS Code's terminal will not reset
// to column 0.
output = output.replace(/\n/g, "\r\n");

switch (category) {
case "stderr":
output = red(output);
break;
case "stdout":
case "userInput":
break;
default:
output = yellow(output);
break;
}

this.emitter.fire(output);
}
}

0 comments on commit 06f3bf1

Please sign in to comment.