Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/editor/codemirror/structure-highlighting/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface CodeBlock {
* Start of the first header node of the run.
*/
start: number;
/** Always past {@link start}, so `bodyStart - 1` is a valid position. */
bodyStart: number;
bodyEnd: number;
/**
Expand Down Expand Up @@ -80,13 +81,19 @@ export const codeBlocks = (state: EditorState): CodeBlock[] => {
let runStart = 0;
for (let i = 0; i < children.length; ++i) {
if (children[i].name === "Body") {
blocks.push({
statement: leaving.name,
start: children[runStart].start,
bodyStart: children[i].start,
bodyEnd: children[i].end,
depth,
});
// Error recovery can give a compound statement a zero-length
// error node in place of its header, leaving the body starting
// where the header does. There's no header to draw, and the
// view relies on bodyStart being past start.
if (children[i].start > children[runStart].start) {
blocks.push({
statement: leaving.name,
start: children[runStart].start,
bodyStart: children[i].start,
bodyEnd: children[i].end,
depth,
});
}
runStart = i + 1;
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/editor/codemirror/structure-highlighting/grammar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,32 @@ describe("structure highlighting grammar contract", () => {
]);
});

it("drops runs whose header is only an error node", () => {
// A leading colon recovers as a TryStatement whose children are a
// zero-length error node then the body, so start and bodyStart are both 0.
// view.ts would take bodyStart - 1 and ask for line -1.
const state = createState(":\n pass\n");
expect(codeBlocks(state)).toEqual([]);
});

it("keeps bodyStart past start for every run", () => {
const samples = [
"if a:\n pass\n",
"if a:\n pass\nelif b:\n pass\nelse:\n pass\n",
"try:\n pass\nexcept:\n pass\nfinally:\n pass\n",
"if a: pass\n",
":\n pass\n",
"::\n pass\n",
" pass\n",
"else:\n pass\n",
];
for (const doc of samples) {
for (const block of codeBlocks(createState(doc))) {
expect(block.bodyStart).toBeGreaterThan(block.start);
}
}
});

it("covers every compound statement name in grammarInfo", () => {
const samples = [
"if a:\n pass\n",
Expand Down
3 changes: 2 additions & 1 deletion src/editor/codemirror/structure-highlighting/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export const codeStructureView = (option: "full" | "simple") =>
*
* @param view The view.
* @param start The start position.
* @param end The end position.
* @param end The end position. Must be greater than zero, as we look
* at the character before it; see CodeBlock.bodyStart.
* @param depth Current indent depth (1 per indent level starting at 0).
* @param parent The parent positions (e.g. for the while block) if we're calculating body positions, otherwise undefined.
* @returns The positions for the block denoted by start/end or undefined if highlighting should be skipped.
Expand Down
Loading