1
1
import * as vscode from 'vscode'
2
- import { TextEdit } from 'vscode'
2
+ import {
3
+ CancellationToken , CancellationTokenSource , CodeLens , CodeLensProvider , Command ,
4
+ Event , EventEmitter , ProgressLocation , Range , TextDocument , TextEdit
5
+ } from 'vscode'
3
6
4
7
import {
5
8
asWorksheetRunParams , WorksheetRunRequest , WorksheetRunParams , WorksheetRunResult ,
@@ -14,11 +17,14 @@ import { Disposable } from 'vscode-jsonrpc'
14
17
*/
15
18
export const worksheetRunKey = "dotty.worksheet.run"
16
19
17
- /** A worksheet managed by vscode */
18
- class Worksheet {
20
+ /**
21
+ * The command key for cancelling a running worksheet. Exposed to users as
22
+ * `Cancel running worksheet`.
23
+ */
24
+ export const worksheetCancelKey = "dotty.worksheet.cancel"
19
25
20
- constructor ( readonly document : vscode . TextDocument , readonly client : BaseLanguageClient ) {
21
- }
26
+ /** A worksheet managed by vscode */
27
+ class Worksheet implements Disposable {
22
28
23
29
/** All decorations that have been added so far */
24
30
private decorationTypes : vscode . TextEditorDecorationType [ ] = [ ]
@@ -32,7 +38,29 @@ class Worksheet {
32
38
/** The minimum margin to add so that the decoration is shown after all text. */
33
39
private margin : number = 0
34
40
35
- /** Remove all decorations and resets this worksheet. */
41
+ private readonly _onDidStateChange : EventEmitter < void > = new EventEmitter ( )
42
+ /** This event is fired when the worksheet starts or stops running. */
43
+ readonly onDidStateChange : Event < void > = this . _onDidStateChange . event
44
+
45
+ /**
46
+ * If this is not null, this can be used to signal cancellation of the
47
+ * currently running worksheet.
48
+ */
49
+ private canceller ?: CancellationTokenSource = undefined
50
+
51
+ constructor ( readonly document : vscode . TextDocument , readonly client : BaseLanguageClient ) {
52
+ }
53
+
54
+ dispose ( ) {
55
+ this . reset ( )
56
+ if ( this . canceller ) {
57
+ this . canceller . dispose ( )
58
+ this . canceller = undefined
59
+ }
60
+ this . _onDidStateChange . dispose ( )
61
+ }
62
+
63
+ /** Remove all decorations, and resets this worksheet. */
36
64
private reset ( ) : void {
37
65
this . decorationTypes . forEach ( decoration => decoration . dispose ( ) )
38
66
this . insertedLines = 0
@@ -51,26 +79,58 @@ class Worksheet {
51
79
return edits
52
80
}
53
81
82
+ /** If this worksheet is currently being run, cancel the run. */
83
+ cancel ( ) : void {
84
+ if ( this . canceller ) {
85
+ this . canceller . cancel ( )
86
+ this . canceller = undefined
87
+
88
+ this . _onDidStateChange . fire ( )
89
+ }
90
+ }
91
+
92
+ /** Is this worksheet currently being run ? */
93
+ isRunning ( ) : boolean {
94
+ return this . canceller != undefined
95
+ }
96
+
54
97
/**
55
- * Run the worksheet in `document`, display a progress bar during the run.
98
+ * Run the worksheet in `document`, if a previous run is in progress, it is
99
+ * cancelled first.
56
100
*/
57
101
run ( ) : Promise < WorksheetRunResult > {
58
- return new Promise ( ( resolve , reject ) => {
102
+ this . cancel ( )
103
+ const canceller = new CancellationTokenSource ( )
104
+ const token = canceller . token
105
+ // This ensures that isRunning() returns true.
106
+ this . canceller = canceller
107
+
108
+ this . _onDidStateChange . fire ( )
109
+
110
+ return new Promise < WorksheetRunResult > ( resolve => {
59
111
const textEdits = this . prepareRun ( )
60
112
const edit = new vscode . WorkspaceEdit ( )
61
113
edit . set ( this . document . uri , textEdits )
62
114
vscode . workspace . applyEdit ( edit ) . then ( editSucceeded => {
63
- if ( editSucceeded ) {
64
- return resolve ( vscode . window . withProgress ( {
65
- location : vscode . ProgressLocation . Notification ,
66
- title : "Run the worksheet" ,
67
- cancellable : true
68
- } , ( _ , token ) => this . client . sendRequest (
115
+ if ( editSucceeded && ! token . isCancellationRequested )
116
+ resolve ( vscode . window . withProgress ( {
117
+ location : ProgressLocation . Window ,
118
+ title : "Running worksheet"
119
+ } , ( ) => this . client . sendRequest (
69
120
WorksheetRunRequest . type , asWorksheetRunParams ( this . document ) , token
70
121
) ) )
71
- } else
72
- reject ( )
122
+ else
123
+ resolve ( { success : false } )
73
124
} )
125
+ } ) . then ( result => {
126
+ canceller . dispose ( )
127
+ if ( this . canceller === canceller ) { // If false, a new run has already started
128
+ // This ensures that isRunning() returns false.
129
+ this . canceller = undefined
130
+
131
+ this . _onDidStateChange . fire ( )
132
+ }
133
+ return result
74
134
} )
75
135
}
76
136
@@ -210,13 +270,20 @@ class Worksheet {
210
270
}
211
271
212
272
export class WorksheetProvider implements Disposable {
213
- private disposables : Disposable [ ] = [ ]
214
273
private worksheets : Map < vscode . TextDocument , Worksheet > = new Map ( )
274
+ private readonly _onDidWorksheetStateChange : EventEmitter < Worksheet > = new EventEmitter ( )
275
+ /** This event is fired when a worksheet starts or stops running. */
276
+ readonly onDidWorksheetStateChange : Event < Worksheet > = this . _onDidWorksheetStateChange . event
277
+
278
+ private disposables : Disposable [ ] = [ this . _onDidWorksheetStateChange ]
215
279
216
280
constructor (
217
281
readonly client : BaseLanguageClient ,
218
- readonly documentSelectors : vscode . DocumentSelector [ ] ) {
282
+ readonly documentSelector : vscode . DocumentSelector ) {
283
+ const codeLensProvider = new WorksheetCodeLensProvider ( this )
219
284
this . disposables . push (
285
+ codeLensProvider ,
286
+ vscode . languages . registerCodeLensProvider ( documentSelector , codeLensProvider ) ,
220
287
vscode . workspace . onWillSaveTextDocument ( event => {
221
288
const worksheet = this . worksheetFor ( event . document )
222
289
if ( worksheet ) {
@@ -231,12 +298,17 @@ export class WorksheetProvider implements Disposable {
231
298
}
232
299
} ) ,
233
300
vscode . workspace . onDidCloseTextDocument ( document => {
234
- if ( this . isWorksheet ( document ) ) {
301
+ const worksheet = this . worksheetFor ( document )
302
+ if ( worksheet ) {
303
+ worksheet . dispose ( )
235
304
this . worksheets . delete ( document )
236
305
}
237
306
} ) ,
238
307
vscode . commands . registerCommand ( worksheetRunKey , ( ) => {
239
- this . runWorksheetCommand ( )
308
+ this . callOnActiveWorksheet ( w => w . run ( ) )
309
+ } ) ,
310
+ vscode . commands . registerCommand ( worksheetCancelKey , ( ) => {
311
+ this . callOnActiveWorksheet ( w => w . cancel ( ) )
240
312
} )
241
313
)
242
314
client . onNotification ( WorksheetPublishOutputNotification . type , params => {
@@ -245,17 +317,19 @@ export class WorksheetProvider implements Disposable {
245
317
}
246
318
247
319
dispose ( ) {
248
- this . disposables . forEach ( d => d . dispose ( ) ) ;
249
- this . disposables = [ ] ;
320
+ this . worksheets . forEach ( d => d . dispose ( ) )
321
+ this . worksheets . clear ( )
322
+ this . disposables . forEach ( d => d . dispose ( ) )
323
+ this . disposables = [ ]
250
324
}
251
325
252
326
/** Is this document a worksheet? */
253
327
private isWorksheet ( document : vscode . TextDocument ) : boolean {
254
- return this . documentSelectors . some ( sel => vscode . languages . match ( sel , document ) > 0 )
328
+ return vscode . languages . match ( this . documentSelector , document ) > 0
255
329
}
256
330
257
331
/** If `document` is a worksheet, create a new worksheet for it, or return the existing one. */
258
- private worksheetFor ( document : vscode . TextDocument ) : Worksheet | undefined {
332
+ worksheetFor ( document : vscode . TextDocument ) : Worksheet | undefined {
259
333
if ( ! this . isWorksheet ( document ) ) return
260
334
else {
261
335
const existing = this . worksheets . get ( document )
@@ -264,20 +338,21 @@ export class WorksheetProvider implements Disposable {
264
338
} else {
265
339
const newWorksheet = new Worksheet ( document , this . client )
266
340
this . worksheets . set ( document , newWorksheet )
341
+ this . disposables . push (
342
+ newWorksheet . onDidStateChange ( ( ) => this . _onDidWorksheetStateChange . fire ( newWorksheet ) )
343
+ )
267
344
return newWorksheet
268
345
}
269
346
}
270
347
}
271
348
272
- /**
273
- * The VSCode command executed when the user select `Run worksheet`.
274
- */
275
- private runWorksheetCommand ( ) {
276
- const editor = vscode . window . activeTextEditor
277
- if ( editor ) {
278
- const worksheet = this . worksheetFor ( editor . document )
349
+ /** If the active text editor contains a worksheet, apply `f` to it. */
350
+ private callOnActiveWorksheet ( f : ( _ : Worksheet ) => void ) {
351
+ let document = vscode . window . activeTextEditor && vscode . window . activeTextEditor . document
352
+ if ( document ) {
353
+ const worksheet = this . worksheetFor ( document )
279
354
if ( worksheet ) {
280
- worksheet . run ( )
355
+ f ( worksheet )
281
356
}
282
357
}
283
358
}
@@ -302,3 +377,39 @@ export class WorksheetProvider implements Disposable {
302
377
}
303
378
}
304
379
}
380
+
381
+ class WorksheetCodeLensProvider implements CodeLensProvider , Disposable {
382
+ private readonly _onDidChangeCodeLenses : EventEmitter < void > = new EventEmitter ( )
383
+ readonly onDidChangeCodeLenses : Event < void > = this . _onDidChangeCodeLenses . event
384
+
385
+ private disposables : Disposable [ ] = [ this . _onDidChangeCodeLenses ]
386
+
387
+ constructor ( readonly worksheetProvider : WorksheetProvider ) {
388
+ this . disposables . push (
389
+ worksheetProvider . onDidWorksheetStateChange ( ( ) => this . _onDidChangeCodeLenses . fire ( ) )
390
+ )
391
+ }
392
+
393
+ dispose ( ) {
394
+ this . disposables . forEach ( d => d . dispose ( ) )
395
+ this . disposables = [ ]
396
+ }
397
+
398
+ private readonly runCommand : Command = {
399
+ command : worksheetRunKey ,
400
+ title : "Run this worksheet"
401
+ }
402
+
403
+ private readonly cancelCommand : Command = {
404
+ command : worksheetCancelKey ,
405
+ title : "Worksheet running, click to cancel"
406
+ }
407
+
408
+ provideCodeLenses ( document : TextDocument , token : CancellationToken ) {
409
+ const worksheet = this . worksheetProvider . worksheetFor ( document )
410
+ if ( worksheet ) {
411
+ const cmd = worksheet . isRunning ( ) ? this . cancelCommand : this . runCommand
412
+ return [ new CodeLens ( new Range ( 0 , 0 , 0 , 0 ) , cmd ) ]
413
+ }
414
+ }
415
+ }
0 commit comments