1+ import { ChildProcess , spawn } from 'child_process' ;
2+ import { commands , ExtensionContext , Uri , WebviewView , WebviewViewProvider , WebviewViewResolveContext , window , workspace } from 'vscode' ;
3+ import { state } from './extension' ;
4+ import { dirname } from 'path' ;
5+ import * as treeKill from 'tree-kill' ;
6+
7+ export default function setupConsole ( context : ExtensionContext ) {
8+ const sketchProcesses : ChildProcess [ ] = [ ] ;
9+
10+ const provider = new ProcessingConsoleViewProvider ( ) ;
11+
12+ const register = window . registerWebviewViewProvider ( 'processingConsoleView' , provider ) ;
13+
14+ const startSketch = commands . registerCommand ( 'processing.sketch.run' , ( resource : Uri ) => {
15+ const autosave = workspace
16+ . getConfiguration ( 'processing' )
17+ . get < boolean > ( 'autosave' ) ;
18+ if ( autosave === true ) {
19+ // Save all files before running the sketch
20+ commands . executeCommand ( 'workbench.action.files.saveAll' ) ;
21+ }
22+ if ( resource == undefined ) {
23+ const editor = window . activeTextEditor ;
24+ if ( editor ) {
25+ resource = editor . document . uri ;
26+ }
27+ }
28+
29+ if ( ! resource ) {
30+ return ;
31+ }
32+ commands . executeCommand ( 'processingConsoleView.focus' ) ;
33+ commands . executeCommand ( 'processing.sketch.stop' ) ;
34+
35+ const proc = spawn (
36+ state . selectedVersion . path ,
37+ [ 'cli' , `--sketch=${ dirname ( resource . fsPath ) } ` , '--run' ] ,
38+ {
39+ shell : false ,
40+ }
41+ ) ;
42+ proc . stdout . on ( "data" , ( data ) => {
43+ if ( proc != sketchProcesses [ 0 ] ) {
44+ // If this is not the most recent process, ignore its output
45+ return ;
46+ }
47+ provider . webview ?. webview . postMessage ( { type : 'stdout' , value : data ?. toString ( ) } ) ;
48+ } ) ;
49+ proc . stderr . on ( "data" , ( data ) => {
50+ if ( proc != sketchProcesses [ 0 ] ) {
51+ // If this is not the most recent process, ignore its output
52+ return ;
53+ }
54+ provider . webview ?. webview . postMessage ( { type : 'stderr' , value : data ?. toString ( ) } ) ;
55+ // TODO: Handle and highlight errors in the editor
56+ } ) ;
57+ proc . on ( 'close' , ( code ) => {
58+ provider . webview ?. webview . postMessage ( { type : 'close' , value : code ?. toString ( ) } ) ;
59+ sketchProcesses . splice ( sketchProcesses . indexOf ( proc ) , 1 ) ;
60+ commands . executeCommand ( 'setContext' , 'processing.sketch.running' , sketchProcesses . length > 0 ) ;
61+ } ) ;
62+ provider . webview ?. show ?.( true ) ;
63+ provider . webview ?. webview . postMessage ( { type : 'clear' } ) ;
64+ sketchProcesses . unshift ( proc ) ;
65+ commands . executeCommand ( 'setContext' , 'processing.sketch.running' , true ) ;
66+ } ) ;
67+
68+ const restartSketch = commands . registerCommand ( 'processing.sketch.restart' , ( resource : Uri ) => {
69+ commands . executeCommand ( 'processing.sketch.run' , resource ) ;
70+ } ) ;
71+
72+ const stopSketch = commands . registerCommand ( 'processing.sketch.stop' , ( ) => {
73+ for ( const proc of sketchProcesses ) {
74+ treeKill ( proc . pid as number ) ;
75+ }
76+ } ) ;
77+
78+ context . subscriptions . push (
79+ register ,
80+ startSketch ,
81+ restartSketch ,
82+ stopSketch
83+ ) ;
84+ }
85+
86+ // TODO: Add setting for timestamps
87+ // TODO: Add setting for collapsing similar messages
88+ // TODO: Add option to enable/disable stdout and stderr
89+ class ProcessingConsoleViewProvider implements WebviewViewProvider {
90+ public webview ?: WebviewView ;
91+
92+ public resolveWebviewView ( webviewView : WebviewView , context : WebviewViewResolveContext ) : Thenable < void > | void {
93+ webviewView . webview . options = { enableScripts : true } ;
94+ webviewView . webview . html = `
95+ <!DOCTYPE html>
96+ <html>
97+ <body>
98+ <script>
99+ window.addEventListener('message', event => {
100+
101+ const message = event.data; // The JSON data our extension sent
102+
103+ const isScrolledToBottom = (window.innerHeight + window.scrollY) >= document.body.offsetHeight;
104+
105+ const ts = document.createElement("span");
106+ ts.style.color = "gray";
107+ const now = new Date();
108+ const hours = now.getHours().toString().padStart(2, '0');
109+ const minutes = now.getMinutes().toString().padStart(2, '0');
110+ const seconds = now.getSeconds().toString().padStart(2, '0');
111+ ts.textContent = "[" + hours + ":" + minutes + ":" + seconds + "] ";
112+
113+
114+ switch (message.type) {
115+ case 'clear':
116+ document.body.innerHTML = '';
117+ break;
118+ case 'stdout':
119+ var pre = document.createElement("pre");
120+ pre.style.color = "white";
121+ pre.textContent = message.value;
122+ if (pre.textContent.endsWith("\\n")) {
123+ pre.textContent = pre.textContent.slice(0, -1);
124+ }
125+ pre.prepend(ts);
126+ document.body.appendChild(pre);
127+ break;
128+ case 'stderr':
129+ var pre = document.createElement("pre");
130+ pre.style.color = "red";
131+ pre.textContent = message.value;
132+ if (pre.textContent.endsWith("\\n")) {
133+ pre.textContent = pre.textContent.slice(0, -1);
134+ }
135+ pre.prepend(ts);
136+ document.body.appendChild(pre);
137+ break;
138+ case 'close':
139+ var pre = document.createElement("pre");
140+ pre.style.color = "gray";
141+ pre.textContent = "Process exited with code " + message.value;
142+ pre.prepend(ts);
143+ document.body.appendChild(pre);
144+ break;
145+ }
146+
147+ if (isScrolledToBottom) {
148+ window.scrollTo(0, document.body.scrollHeight);
149+ }
150+ });
151+ </script>
152+ </body>
153+ </html>
154+ ` ;
155+ webviewView . onDidDispose ( ( ) => {
156+ commands . executeCommand ( "processing.sketch.stop" ) ;
157+ } ) ;
158+ this . webview = webviewView ;
159+ }
160+
161+ }
0 commit comments