Skip to content

Commit f39dbdd

Browse files
committed
Show worksheet evaluation progress
Also, add a command to evaluate worksheet: "Run worksheet"
1 parent 268aff0 commit f39dbdd

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ class DottyLanguageServer extends LanguageServer
233233
val driver = driverFor(uri)
234234
val sendMessage = (msg: String) => client.logMessage(new MessageParams(MessageType.Info, uri + msg))
235235
evaluateWorksheet(driver, uri, sendMessage)(driver.currentCtx)
236+
sendMessage("FINISHED")
236237
}
237238
}
238239
}

vscode-dotty/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
}
4040
],
4141
"contributes": {
42+
"commands": [
43+
{
44+
"command": "worksheet.evaluate",
45+
"title": "Run worksheet"
46+
}
47+
],
4248
"configurationDefaults": {
4349
"[scala]": {
4450
"editor.tabSize": 2,

vscode-dotty/src/extension.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,22 @@ import * as worksheet from './worksheet'
1212

1313
let extensionContext: ExtensionContext
1414
let outputChannel: vscode.OutputChannel
15+
let client: LanguageClient
1516

1617
export function activate(context: ExtensionContext) {
1718
extensionContext = context
1819
outputChannel = vscode.window.createOutputChannel('Dotty Language Client');
1920

2021
vscode.workspace.onWillSaveTextDocument(worksheet.prepareWorksheet)
22+
vscode.workspace.onDidSaveTextDocument(document => {
23+
if (worksheet.isWorksheet(document)) {
24+
vscode.commands.executeCommand(worksheet.worksheetEvaluateAfterSaveKey)
25+
}
26+
})
27+
28+
vscode.commands.registerCommand(worksheet.worksheetEvaluateAfterSaveKey, () => {
29+
worksheet.evaluateCommand()
30+
})
2131

2232
const artifactFile = `${vscode.workspace.rootPath}/.dotty-ide-artifact`
2333
fs.readFile(artifactFile, (err, data) => {
@@ -104,7 +114,7 @@ function run(serverOptions: ServerOptions) {
104114

105115
outputChannel.dispose()
106116

107-
const client = new LanguageClient('dotty', 'Dotty Language Server', serverOptions, clientOptions);
117+
client = new LanguageClient('dotty', 'Dotty Language Server', serverOptions, clientOptions);
108118

109119
// We use the `window/logMessage` command to communicate back the result of evaluating
110120
// a worksheet.
@@ -114,6 +124,10 @@ function run(serverOptions: ServerOptions) {
114124
})
115125
})
116126

127+
vscode.commands.registerCommand(worksheet.worksheetEvaluateKey, () => {
128+
worksheet.worksheetSave(client)
129+
})
130+
117131
// Push the disposable to the context's subscriptions so that the
118132
// client can be deactivated on extension deactivation
119133
extensionContext.subscriptions.push(client.start());

vscode-dotty/src/worksheet.ts

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
import * as vscode from 'vscode'
4+
import { LanguageClient } from 'vscode-languageclient'
45

56
/** All decorations that have been added so far */
67
let worksheetDecorationTypes: Map<vscode.TextDocument, vscode.TextEditorDecorationType[]> = new Map<vscode.TextDocument, vscode.TextEditorDecorationType[]>()
@@ -11,6 +12,75 @@ let worksheetInsertedLines: Map<vscode.TextDocument, number> = new Map<vscode.Te
1112
/** The minimum margin to add so that the decoration is shown after all text. */
1213
let worksheetMargin: Map<vscode.TextDocument, number> = new Map<vscode.TextDocument, number>()
1314

15+
/** Whether the given worksheet has finished evaluating. */
16+
let worksheetFinished: Map<vscode.TextDocument, boolean> = new Map<vscode.TextDocument, boolean>()
17+
18+
/**
19+
* The command key for evaluating a worksheet. Exposed to users as
20+
* `Run worksheet`.
21+
*/
22+
export const worksheetEvaluateKey = "worksheet.evaluate"
23+
24+
/**
25+
* The command that is called to evaluate a worksheet after it has been evaluated.
26+
*
27+
* This is not exposed as a standalone callable command; but this command is triggered
28+
* when a worksheet is saved.
29+
*/
30+
export const worksheetEvaluateAfterSaveKey = "worksheet.evaluateAfterSave"
31+
32+
/** Is this document a worksheet? */
33+
export function isWorksheet(document: vscode.TextDocument): boolean {
34+
return document.fileName.endsWith(".sc")
35+
}
36+
37+
/**
38+
* This command is bound to `worksheetEvaluateAfterSaveKey`. This is implemented
39+
* as a command, because we want to display a progress bar that may stay for a while, and
40+
* VSCode will kill promises triggered by file save after some time.
41+
*/
42+
export function evaluateCommand() {
43+
const editor = vscode.window.activeTextEditor
44+
if (editor) {
45+
const document = editor.document
46+
if (isWorksheet(document)) {
47+
showWorksheetProgress(document)
48+
}
49+
}
50+
}
51+
52+
/**
53+
* The VSCode command executed when the user select `Run worksheet`.
54+
*
55+
* We check whether the buffer is dirty, and if it is, we save it. Evaluation will then be
56+
* triggered by file save.
57+
* If the buffer is clean, we do the necessary preparation for worksheet (compute margin,
58+
* remove blank lines, etc.) and check if the buffer has been changed by that. If it is, we save
59+
* and the evaluation will be triggered by file save.
60+
* If the buffer is still clean, we send a `textDocument/didSave` notification to the language
61+
* server in order to start the execution of the worksheet.
62+
*/
63+
export function worksheetSave(client: LanguageClient) {
64+
const editor = vscode.window.activeTextEditor
65+
if (editor) {
66+
const document = editor.document
67+
if (isWorksheet(document)) {
68+
if (document.isDirty) document.save()
69+
else {
70+
_prepareWorksheet(document).then(_ => {
71+
if (document.isDirty) document.save()
72+
else {
73+
client.sendNotification("textDocument/didSave", {
74+
textDocument: { uri: document.uri.toString() }
75+
})
76+
showWorksheetProgress(document)
77+
}
78+
})
79+
}
80+
}
81+
}
82+
}
83+
1484
/**
1585
* If the document that will be saved is a worksheet, resets the "worksheet state"
1686
* (margin and number of inserted lines), and removes redundant blank lines that
@@ -22,18 +92,45 @@ let worksheetMargin: Map<vscode.TextDocument, number> = new Map<vscode.TextDocum
2292
*/
2393
export function prepareWorksheet(event: vscode.TextDocumentWillSaveEvent) {
2494
const document = event.document
25-
if (document.fileName.endsWith(".sc")) {
26-
const setup =
27-
removeRedundantBlankLines(document)
95+
const setup = _prepareWorksheet(document)
96+
event.waitUntil(setup)
97+
}
98+
99+
function _prepareWorksheet(document: vscode.TextDocument) {
100+
if (isWorksheet(document)) {
101+
return removeRedundantBlankLines(document)
28102
.then(_ => {
29103
removeDecorations(document)
30104
worksheetMargin.set(document, longestLine(document) + 5)
31105
worksheetInsertedLines.set(document, 0)
106+
worksheetFinished.set(document, false)
32107
})
33-
event.waitUntil(setup)
108+
} else {
109+
return Promise.resolve()
34110
}
35111
}
36112

113+
function showWorksheetProgress(document: vscode.TextDocument) {
114+
return vscode.window.withProgress({
115+
location: vscode.ProgressLocation.Window,
116+
title: "Evaluating worksheet"
117+
}, _ => {
118+
function isFinished() {
119+
return worksheetFinished.get(document) || false
120+
}
121+
return wait(isFinished, 500)
122+
})
123+
}
124+
125+
/** Wait until `cond` evaluates to true; test every `delay` ms. */
126+
function wait(cond: () => boolean, delayMs: number): Promise<boolean> {
127+
const isFinished = cond()
128+
if (isFinished) {
129+
return Promise.resolve(true)
130+
}
131+
else return new Promise(fn => setTimeout(fn, delayMs)).then(_ => wait(cond, delayMs))
132+
}
133+
37134
/**
38135
* Handle the result of evaluating part of a worksheet.
39136
* This is called when we receive a `window/logMessage`.
@@ -49,7 +146,11 @@ export function worksheetHandleMessage(message: string) {
49146

50147
if (editor) {
51148
let payload = message.slice(editor.document.uri.toString().length)
52-
worksheetDisplayResult(payload, editor)
149+
if (payload == "FINISHED") {
150+
worksheetFinished.set(editor.document, true)
151+
} else {
152+
worksheetDisplayResult(payload, editor)
153+
}
53154
}
54155
}
55156

0 commit comments

Comments
 (0)