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