Skip to content

Commit 3c89d53

Browse files
authored
Merge pull request #5582 from dotty-staging/fix/worksheet-multiline
Rework multi-line output handling in the worksheet
2 parents 9ed0524 + fa5bdbe commit 3c89d53

File tree

4 files changed

+41
-154
lines changed

4 files changed

+41
-154
lines changed

project/Build.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ object Build {
991991
settings(commonSettings).
992992
settings(
993993
EclipseKeys.skipProject := true,
994-
version := "0.1.11-snapshot", // Keep in sync with package.json
994+
version := "0.1.12-snapshot", // Keep in sync with package.json
995995
autoScalaLibrary := false,
996996
publishArtifact := false,
997997
includeFilter in unmanagedSources := NothingFilter | "*.ts" | "**.json",

vscode-dotty/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vscode-dotty/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "dotty",
33
"displayName": "Dotty Language Server",
44
"description": "IDE integration for Dotty, the experimental Scala compiler",
5-
"version": "0.1.11-snapshot",
5+
"version": "0.1.12-snapshot",
66
"license": "BSD-3-Clause",
77
"publisher": "lampepfl",
88
"repository": {

vscode-dotty/src/worksheet.ts

Lines changed: 38 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ class Worksheet implements Disposable {
5151
/** All decorations that have been added so far */
5252
private decorations: Decoration[] = []
5353

54-
/** The number of blank lines that have been inserted to fit the output so far. */
55-
private insertedLines: number = 0
56-
57-
/** The lines that contain decorations */
58-
private decoratedLines: Set<number> = new Set<number>()
59-
6054
/** The minimum margin to add so that the decoration is shown after all text. */
6155
private margin: number = 0
6256

@@ -70,46 +64,22 @@ class Worksheet implements Disposable {
7064
*/
7165
private canceller?: CancellationTokenSource = undefined
7266

73-
/**
74-
* The edits that should be applied to this worksheet.
75-
*
76-
* This is used to ensure that the blank lines added to fit the output of the worksheet
77-
* are inserted in the same order as the output arrived.
78-
*/
79-
private applyEdits: Promise<void> = Promise.resolve()
80-
8167
constructor(readonly document: vscode.TextDocument, readonly client: BaseLanguageClient) {
8268
}
8369

8470
dispose() {
8571
this.reset()
86-
if (this.canceller) {
87-
this.canceller.dispose()
88-
this.canceller = undefined
89-
}
9072
this._onDidStateChange.dispose()
9173
}
9274

93-
/** Remove all decorations, and resets this worksheet. */
75+
/** Cancel any current run, remove all decorations, and resets this worksheet. */
9476
private reset(): void {
77+
this.cancel()
78+
9579
this.decorations.forEach(decoration => decoration.decorationType.dispose())
9680
this.decorations = []
97-
this.insertedLines = 0
98-
this.decoratedLines.clear()
9981
this.runVersion = -1
10082
this.margin = this.longestLine() + 5
101-
this.applyEdits = Promise.resolve()
102-
}
103-
104-
/**
105-
* Reset the "worksheet state" (margin and number of inserted lines), and
106-
* return an array of TextEdit that remove the redundant blank lines that have
107-
* been inserted by a previous run.
108-
*/
109-
prepareRun(): TextEdit[] {
110-
const edits = this.removeRedundantBlankLinesEdits()
111-
this.reset()
112-
return edits
11383
}
11484

11585
/** If this worksheet is currently being run, cancel the run. */
@@ -129,9 +99,7 @@ class Worksheet implements Disposable {
12999

130100
/** Display the output in the worksheet's editor. */
131101
handleMessage(output: WorksheetPublishOutputParams, editor: vscode.TextEditor) {
132-
this.applyEdits = this.applyEdits.then(() => {
133-
this.displayAndSaveResult(output.line - 1, output.content, editor)
134-
})
102+
this.displayAndSaveResult(output.line - 1, output.content, editor)
135103
}
136104

137105
/**
@@ -140,29 +108,22 @@ class Worksheet implements Disposable {
140108
*/
141109
run(): Promise<WorksheetRunResult> {
142110
this.cancel()
111+
this.reset()
143112
const canceller = new CancellationTokenSource()
144113
const token = canceller.token
145-
// This ensures that isRunning() returns true.
146-
this.canceller = canceller
114+
this.canceller = canceller // This ensures that isRunning() returns true.
147115

148116
this._onDidStateChange.fire()
149117

150118
return new Promise<WorksheetRunResult>(resolve => {
151-
const textEdits = this.prepareRun()
152-
const edit = new vscode.WorkspaceEdit()
153-
edit.set(this.document.uri, textEdits)
154-
vscode.workspace.applyEdit(edit).then(editSucceeded => {
155-
this.runVersion = this.document.version
156-
if (editSucceeded && !token.isCancellationRequested)
157-
resolve(vscode.window.withProgress({
158-
location: ProgressLocation.Window,
159-
title: "Running worksheet"
160-
}, () => this.client.sendRequest(
161-
WorksheetRunRequest.type, asWorksheetRunParams(this.document), token
162-
)))
163-
else
164-
resolve({ success: false })
165-
})
119+
this.runVersion = this.document.version
120+
resolve(
121+
vscode.window.withProgress({
122+
location: ProgressLocation.Window,
123+
title: "Running worksheet"
124+
}, () => this.client.sendRequest(
125+
WorksheetRunRequest.type, asWorksheetRunParams(this.document), token
126+
)))
166127
}).then(result => {
167128
canceller.dispose()
168129
if (this.canceller === canceller) { // If false, a new run has already started
@@ -181,43 +142,25 @@ class Worksheet implements Disposable {
181142
*
182143
* @param lineNumber The number of the line in the source that produced the result.
183144
* @param runResult The result itself.
184-
* @param worksheet The worksheet that receives the result.
185145
* @param editor The editor where to display the result.
186-
* @return A `Promise` that will insert necessary lines to fit the output
187-
* and display the decorations upon completion.
188146
*/
189-
public async displayAndSaveResult(lineNumber: number, runResult: string, editor: vscode.TextEditor): Promise<void> {
190-
const resultLines = runResult.trim().split(/\r\n|\r|\n/g)
191-
192-
// The line where the next decoration should be put.
193-
// It's the number of the line that produced the output, plus the number
194-
// of lines that we've inserted so far.
195-
let actualLine = lineNumber + this.insertedLines
196-
197-
// If the output has more than one line, we need to insert blank lines
198-
// below the line that produced the output to fit the output.
199-
const addNewLinesEdit = new vscode.WorkspaceEdit()
200-
if (resultLines.length > 1) {
201-
const linesToInsert = resultLines.length - 1
202-
const editPos = new vscode.Position(actualLine + 1, 0) // add after the line
203-
addNewLinesEdit.insert(editor.document.uri, editPos, "\n".repeat(linesToInsert))
204-
this.insertedLines += linesToInsert
205-
// Increase the `runVersion`, because the text edit will increase the document's version
206-
this.runVersion += 1
207-
}
147+
private displayAndSaveResult(lineNumber: number, runResult: string, editor: vscode.TextEditor): void {
148+
const resultLines = runResult.split(/\r\n|\r|\n/g)
208149

209-
await vscode.workspace.applyEdit(addNewLinesEdit);
210-
for (let line of resultLines) {
211-
const decorationPosition = new vscode.Position(actualLine, 0);
212-
const decorationMargin = this.margin - editor.document.lineAt(actualLine).text.length;
213-
const decorationType = this.createDecoration(decorationMargin, line);
214-
const decorationOptions = { range: new vscode.Range(decorationPosition, decorationPosition), hoverMessage: line };
215-
const decoration = new Decoration(decorationType, decorationOptions);
216-
this.decoratedLines.add(actualLine);
217-
this.decorations.push(decoration);
218-
editor.setDecorations(decorationType, [decorationOptions]);
219-
actualLine += 1;
150+
if (resultLines.length == 0)
151+
return
152+
153+
const line = editor.document.lineAt(lineNumber)
154+
const decorationOptions = {
155+
range: line.range,
156+
hoverMessage: new vscode.MarkdownString().appendCodeblock(runResult)
220157
}
158+
const decorationMargin = this.margin - line.text.length
159+
const decorationText = resultLines[0] + (resultLines.length > 1 ? `<${resultLines.length - 1} lines hidden>` : "")
160+
const decorationType = this.createDecoration(decorationMargin, decorationText)
161+
const decoration = new Decoration(decorationType, decorationOptions)
162+
this.decorations.push(decoration)
163+
editor.setDecorations(decorationType, [decorationOptions])
221164
}
222165

223166
/**
@@ -226,7 +169,7 @@ class Worksheet implements Disposable {
226169
*
227170
* @param editor The editor where to display the decorations.
228171
*/
229-
public restoreDecorations(editor: vscode.TextEditor) {
172+
restoreDecorations(editor: vscode.TextEditor) {
230173
if (editor.document.version == this.runVersion) {
231174
this.decorations.forEach(decoration => {
232175
editor.setDecorations(decoration.decorationType, [decoration.decorationOptions])
@@ -274,59 +217,6 @@ class Worksheet implements Disposable {
274217

275218
return maxLength
276219
}
277-
278-
/**
279-
* TextEdits to remove the repeated blank lines in the source.
280-
*
281-
* Running a worksheet can insert new lines in the worksheet so that the
282-
* output of a line fits below the line. Before a run, we remove blank
283-
* lines in the worksheet to keep its length under control.
284-
*
285-
* @param worksheet The worksheet where blank lines must be removed.
286-
* @return An array of `TextEdit` that remove the blank lines.
287-
*/
288-
private removeRedundantBlankLinesEdits(): TextEdit[] {
289-
290-
const document = this.document
291-
const lineCount = document.lineCount
292-
let rangesToRemove: vscode.Range[] = []
293-
let rangeStart = 0
294-
let rangeEnd = 0
295-
let inRange = true
296-
297-
function addRange() {
298-
inRange = false
299-
if (rangeStart < rangeEnd) {
300-
rangesToRemove.push(new vscode.Range(rangeStart, 0, rangeEnd, 0))
301-
}
302-
return
303-
}
304-
305-
for (let i = 0; i < lineCount; ++i) {
306-
const isEmpty = document.lineAt(i).isEmptyOrWhitespace && this.hasDecoration(i)
307-
if (inRange) {
308-
if (isEmpty) rangeEnd += 1
309-
else addRange()
310-
} else {
311-
if (isEmpty) {
312-
rangeStart = i
313-
rangeEnd = i + 1
314-
inRange = true
315-
}
316-
}
317-
}
318-
319-
if (inRange) {
320-
rangeEnd = lineCount
321-
addRange()
322-
}
323-
324-
return rangesToRemove.reverse().map(range => vscode.TextEdit.delete(range))
325-
}
326-
327-
private hasDecoration(line: number): boolean {
328-
return this.decoratedLines.has(line)
329-
}
330220
}
331221

332222
export class WorksheetProvider implements Disposable {
@@ -347,17 +237,14 @@ export class WorksheetProvider implements Disposable {
347237
vscode.workspace.onWillSaveTextDocument(event => {
348238
const document = event.document
349239
const worksheet = this.worksheetFor(document)
350-
if (worksheet) {
351-
event.waitUntil(Promise.resolve(worksheet.prepareRun()))
352-
// If the document is not dirty, then `onDidSaveTextDocument` will not
353-
// be called so we need to run the worksheet now.
354-
// On the other hand, if the document _is_ dirty, we should _not_ run
355-
// the worksheet now because the server state will not be synchronized
356-
// with the client state, instead we let `onDidSaveTextDocument`
357-
// handle it.
358-
if (runWorksheetOnSave() && !document.isDirty) {
359-
worksheet.run()
360-
}
240+
// If the document is not dirty, then `onDidSaveTextDocument` will not
241+
// be called so we need to run the worksheet now.
242+
// On the other hand, if the document _is_ dirty, we should _not_ run
243+
// the worksheet now because the server state will not be synchronized
244+
// with the client state, instead we let `onDidSaveTextDocument`
245+
// handle it.
246+
if (worksheet && runWorksheetOnSave() && !document.isDirty) {
247+
worksheet.run()
361248
}
362249
}),
363250
vscode.workspace.onDidSaveTextDocument(document => {

0 commit comments

Comments
 (0)