Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 86 additions & 34 deletions client/src/setupConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,43 @@ export default function setupConsole(context: ExtensionContext) {
shell: false,
}
);

// Line buffering to avoid fragmenting output across multiple timestamps
let stdoutBuffer = '';
let stderrBuffer = '';

const sendCompleteLines = (buffer: string, type: 'stdout' | 'stderr'): string => {
const lines = buffer.split('\n');
// All but the last element are complete lines
for (let i = 0; i < lines.length - 1; i++) {
provider.webview?.webview.postMessage({ type, value: lines[i] + '\n' });
}
// Return the incomplete last line (or empty string if buffer ended with \n)
return lines[lines.length - 1];
};

proc.stdout.on("data", (data) => {
if (proc != sketchProcesses[0]) {
// If this is not the most recent process, ignore its output
return;
}
provider.webview?.webview.postMessage({ type: 'stdout', value: data?.toString() });
stdoutBuffer += data?.toString();
stdoutBuffer = sendCompleteLines(stdoutBuffer, 'stdout');
});
proc.stderr.on("data", (data) => {
if (proc != sketchProcesses[0]) {
// If this is not the most recent process, ignore its output
return;
}
provider.webview?.webview.postMessage({ type: 'stderr', value: data?.toString() });
// TODO: Handle and highlight errors in the editor
stderrBuffer += data?.toString();
stderrBuffer = sendCompleteLines(stderrBuffer, 'stderr');
});
proc.on('close', (code) => {
// Flush any remaining buffered output
if (stdoutBuffer) {
provider.webview?.webview.postMessage({ type: 'stdout', value: stdoutBuffer });
}
if (stderrBuffer) {
provider.webview?.webview.postMessage({ type: 'stderr', value: stderrBuffer });
}
provider.webview?.webview.postMessage({ type: 'close', value: code?.toString() });
sketchProcesses.splice(sketchProcesses.indexOf(proc), 1);
commands.executeCommand('setContext', 'processing.sketch.running', sketchProcesses.length > 0);
Expand Down Expand Up @@ -105,53 +126,84 @@ class ProcessingConsoleViewProvider implements WebviewViewProvider {
webviewView.webview.html = `
<!DOCTYPE html>
<html>
<head>
<style>
body {
color: var(--vscode-editor-foreground);
background-color: var(--vscode-editor-background);
margin: 0;
padding: 8px;
font-family: var(--vscode-editor-font-family);
font-size: var(--vscode-editor-font-size);
}

pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
}

.console-timestamp {
color: var(--vscode-descriptionForeground);
}

.console-stdout {
color: var(--vscode-editor-foreground);
}

.console-stderr {
color: var(--vscode-editorError-foreground);
}

.console-close {
color: var(--vscode-descriptionForeground);
}
</style>
</head>
<body>
<script>
window.addEventListener('message', event => {

const message = event.data; // The JSON data our extension sent

const isScrolledToBottom = (window.innerHeight + window.scrollY) >= document.body.offsetHeight;

const ts = document.createElement("span");
ts.style.color = "gray";
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
ts.textContent = "[" + hours + ":" + minutes + ":" + seconds + "] ";
const createTimestampText = () => {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
return "[" + hours + ":" + minutes + ":" + seconds + "] ";
};

const appendConsoleLine = (lineClass, text) => {
const pre = document.createElement("pre");
pre.className = lineClass;

const ts = document.createElement("span");
ts.className = "console-timestamp";
const timestampText = createTimestampText();
const continuationPrefix = " ".repeat(timestampText.length);
const normalizedText = String(text ?? "").replace(/\\n$/, "");
ts.textContent = timestampText;
pre.textContent = normalizedText.replaceAll("\\n", "\\n" + continuationPrefix);

pre.prepend(ts);
document.body.appendChild(pre);
};


switch (message.type) {
case 'clear':
document.body.innerHTML = '';
break;
case 'stdout':
var pre = document.createElement("pre");
pre.style.color = "white";
pre.textContent = message.value;
if (pre.textContent.endsWith("\\n")) {
pre.textContent = pre.textContent.slice(0, -1);
}
pre.prepend(ts);
document.body.appendChild(pre);
appendConsoleLine("console-stdout", message.value);
break;
case 'stderr':
var pre = document.createElement("pre");
pre.style.color = "red";
pre.textContent = message.value;
if (pre.textContent.endsWith("\\n")) {
pre.textContent = pre.textContent.slice(0, -1);
}
pre.prepend(ts);
document.body.appendChild(pre);
appendConsoleLine("console-stderr", message.value);
break;
case 'close':
var pre = document.createElement("pre");
pre.style.color = "gray";
pre.textContent = "Process exited with code " + message.value;
pre.prepend(ts);
document.body.appendChild(pre);
appendConsoleLine("console-close", "Process exited with code " + message.value);
break;
}

Expand Down