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+ provider . webview ?. webview . postMessage ( { type : 'stdout' , value : data ?. toString ( ) } ) ;
44+ } ) ;
45+ proc . stderr . on ( "data" , ( data ) => {
46+ provider . webview ?. webview . postMessage ( { type : 'stderr' , value : data ?. toString ( ) } ) ;
47+ // TODO: Handle and highlight errors in the editor
48+ } ) ;
49+ proc . on ( 'close' , ( code ) => {
50+ provider . webview ?. webview . postMessage ( { type : 'close' , value : code ?. toString ( ) } ) ;
51+ sketchProcesses . splice ( sketchProcesses . indexOf ( proc ) , 1 ) ;
52+ commands . executeCommand ( 'setContext' , 'processing.sketch.running' , false ) ;
53+ } ) ;
54+ provider . webview ?. show ?.( true ) ;
55+ provider . webview ?. webview . postMessage ( { type : 'clear' } ) ;
56+ sketchProcesses . push ( proc ) ;
57+ commands . executeCommand ( 'setContext' , 'processing.sketch.running' , true ) ;
58+ } ) ;
59+
60+ const restartSketch = commands . registerCommand ( 'processing.sketch.restart' , ( resource : Uri ) => {
61+ commands . executeCommand ( 'processing.sketch.run' , resource ) ;
62+ } ) ;
63+
64+ const stopSketch = commands . registerCommand ( 'processing.sketch.stop' , ( ) => {
65+ for ( const proc of sketchProcesses ) {
66+ treeKill ( proc . pid as number ) ;
67+ }
68+ } ) ;
69+
70+ context . subscriptions . push (
71+ register ,
72+ startSketch ,
73+ restartSketch ,
74+ stopSketch
75+ ) ;
76+ }
77+
78+ // TODO: Add setting for timestamps
79+ // TODO: Add setting for collapsing similar messages
80+ // TODO: Add option to enable/disable stdout and stderr
81+ class ProcessingConsoleViewProvider implements WebviewViewProvider {
82+ public webview ?: WebviewView ;
83+
84+ public resolveWebviewView ( webviewView : WebviewView , context : WebviewViewResolveContext ) : Thenable < void > | void {
85+ webviewView . webview . options = { enableScripts : true } ;
86+ webviewView . webview . html = `
87+ <!DOCTYPE html>
88+ <html>
89+ <body>
90+ <script>
91+ window.addEventListener('message', event => {
92+
93+ const message = event.data; // The JSON data our extension sent
94+
95+ const isScrolledToBottom = (window.innerHeight + window.scrollY) >= document.body.offsetHeight;
96+
97+ const ts = document.createElement("span");
98+ ts.style.color = "gray";
99+ const now = new Date();
100+ const hours = now.getHours().toString().padStart(2, '0');
101+ const minutes = now.getMinutes().toString().padStart(2, '0');
102+ const seconds = now.getSeconds().toString().padStart(2, '0');
103+ ts.textContent = "[" + hours + ":" + minutes + ":" + seconds + "] ";
104+
105+
106+ switch (message.type) {
107+ case 'clear':
108+ document.body.innerHTML = '';
109+ break;
110+ case 'stdout':
111+ var pre = document.createElement("pre");
112+ pre.style.color = "white";
113+ pre.textContent = message.value;
114+ if (pre.textContent.endsWith("\\n")) {
115+ pre.textContent = pre.textContent.slice(0, -1);
116+ }
117+ pre.prepend(ts);
118+ document.body.appendChild(pre);
119+ break;
120+ case 'stderr':
121+ var pre = document.createElement("pre");
122+ pre.style.color = "red";
123+ pre.textContent = message.value;
124+ if (pre.textContent.endsWith("\\n")) {
125+ pre.textContent = pre.textContent.slice(0, -1);
126+ }
127+ pre.prepend(ts);
128+ document.body.appendChild(pre);
129+ break;
130+ case 'close':
131+ var pre = document.createElement("pre");
132+ pre.style.color = "gray";
133+ pre.textContent = "Process exited with code " + message.value;
134+ pre.prepend(ts);
135+ document.body.appendChild(pre);
136+ break;
137+ }
138+
139+ if (isScrolledToBottom) {
140+ window.scrollTo(0, document.body.scrollHeight);
141+ }
142+ });
143+ </script>
144+ </body>
145+ </html>
146+ ` ;
147+ webviewView . onDidDispose ( ( ) => {
148+ commands . executeCommand ( "processing.sketch.stop" ) ;
149+ } ) ;
150+ this . webview = webviewView ;
151+ }
152+
153+ }
0 commit comments