Skip to content

Commit 2c3afac

Browse files
lionel-vezwork
andauthored
Fix cell formatting issues (#754)
* Sort text edits by descending start position * Don't append lines to block code before formatting * Don't apply partial edits * Add cell-format test * don't append newline in vdoc, fix edits out-of-range check * Remove test * Undo unecessary `virtualDocForCode` changes * Add changelog entry --------- Co-authored-by: elliot <elliot.evans@posit.co>
1 parent 5d7cdf6 commit 2c3afac

3 files changed

Lines changed: 31 additions & 9 deletions

File tree

apps/vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Fixed Copilot completions in `.qmd` documents (<https://github.com/quarto-dev/quarto/pull/887>).
66
- Fixed a bug where the `autoDetectColorScheme` setting could cause equation previews to have a dark text on dark background and vice versa (<https://github.com/quarto-dev/quarto/pull/864>).
77
- Fix a regression where bash cell execution does not work (<https://github.com/quarto-dev/quarto/pull/826>).
8+
- Fix cell formatting sometimes deleting code at the end of the cell (<https://github.com/quarto-dev/quarto/pull/754>).
89

910
## 1.128.0 (Release on 2026-01-08)
1011

apps/vscode/src/providers/format.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,13 @@ class FormatCellCommand implements Command {
138138
const edits = await formatActiveCell(editor, this.engine_);
139139
if (edits) {
140140
editor.edit((editBuilder) => {
141-
edits.forEach((edit) => {
142-
editBuilder.replace(edit.range, edit.newText);
143-
});
141+
// Sort edits by descending start position to avoid range shifting issues
142+
edits
143+
.slice()
144+
.sort((a, b) => b.range.start.compareTo(a.range.start))
145+
.forEach((edit) => {
146+
editBuilder.replace(edit.range, edit.newText);
147+
});
144148
});
145149
} else {
146150
window.showInformationMessage(
@@ -204,15 +208,20 @@ async function formatActiveCell(editor: TextEditor, engine: MarkdownEngine) {
204208
}
205209

206210
async function formatBlock(doc: TextDocument, block: TokenMath | TokenCodeBlock, language: EmbeddedLanguage) {
207-
const blockLines = lines(codeForExecutableLanguageBlock(block));
208-
blockLines.push("");
211+
// Create virtual document containing the block
212+
const blockLines = lines(codeForExecutableLanguageBlock(block, false));
209213
const vdoc = virtualDocForCode(blockLines, language);
214+
210215
const edits = await executeFormatDocumentProvider(
211216
vdoc,
212217
doc,
213218
formattingOptions(doc.uri, vdoc.language)
214219
);
220+
215221
if (edits) {
222+
// Because we format with the block code copied in an empty virtual
223+
// document, we need to adjust the ranges to match the edits to the block
224+
// cell in the original file.
216225
const blockRange = new Range(
217226
new Position(block.range.start.line, block.range.start.character),
218227
new Position(block.range.end.line, block.range.end.character)
@@ -224,8 +233,17 @@ async function formatBlock(doc: TextDocument, block: TokenMath | TokenCodeBlock,
224233
new Position(edit.range.end.line + block.range.start.line + 1, edit.range.end.character)
225234
);
226235
return new TextEdit(range, edit.newText);
227-
})
228-
.filter(edit => blockRange.contains(edit.range));
236+
});
237+
238+
// Bail if any edit is out of range. We used to filter these edits out but
239+
// this could bork the cell.
240+
if (adjustedEdits.some(edit => !blockRange.contains(edit.range))) {
241+
window.showInformationMessage(
242+
"Formatting edits were out of range and could not be applied to the code cell."
243+
);
244+
return [];
245+
}
246+
229247
return adjustedEdits;
230248
}
231249
}

packages/quarto-core/src/markdown/language.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ export function isExecutableLanguageBlock(token: Token) : token is TokenMath | T
3838
}
3939
}
4040

41-
export function codeForExecutableLanguageBlock(token: TokenMath | TokenCodeBlock) {
41+
export function codeForExecutableLanguageBlock(
42+
token: TokenMath | TokenCodeBlock,
43+
appendNewline = true,
44+
) {
4245
if (isMath(token)) {
4346
return token.data.text;
4447
} else if (isCodeBlock(token)) {
45-
return token.data + "\n";
48+
return token.data + (appendNewline ? "\n" : "");
4649
} else {
4750
return "";
4851
}

0 commit comments

Comments
 (0)