Skip to content
Open
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
82 changes: 81 additions & 1 deletion apps/vscode/src/providers/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,28 @@ async function setEditorHighlightDecorations(
// ranges to highlight
const blockRanges: vscode.Range[] = [];
const inlineRanges: vscode.Range[] = [];
const optionLineRanges: vscode.Range[] = [];
const optionSeparatorRanges: vscode.Range[] = [];

if (highlightingConfig.enabled()) {

// find code blocks
const tokens = engine.parse(editor.document);
for (const block of tokens.filter(isExecutableLanguageBlock)) {
blockRanges.push(vscRange(block.range));
const blockRange = vscRange(block.range);
blockRanges.push(blockRange);

// cell options (#| comments) get a darker background, and the last
// option line gets a separator (rendered as a bottom border)
const lines = hashPipeLines(editor.document, blockRange);
for (const line of lines) {
optionLineRanges.push(editor.document.lineAt(line).range);
}
if (lines.length > 0) {
optionSeparatorRanges.push(
editor.document.lineAt(lines[lines.length - 1]).range
);
}
}

// find inline executable code
Expand All @@ -186,10 +201,75 @@ async function setEditorHighlightDecorations(
highlightingConfig.inlineBackgroundDecoration(),
inlineRanges
);
editor.setDecorations(cellOptionsBackgroundDecoration, optionLineRanges);
editor.setDecorations(cellOptionsSeparatorDecoration, optionSeparatorRanges);
}

function clearEditorHighlightDecorations(editor: vscode.TextEditor) {
editor.setDecorations(highlightingConfig.backgroundDecoration(), []);
editor.setDecorations(cellOptionsBackgroundDecoration, []);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clears the block decoration and both new cell option decorations, but not highlightingConfig.inlineBackgroundDecoration(). When the document's language mode changes, the inline backgrounds for `r ...` code stay behind on the non-Quarto document. That omission existed before this PR, but since we are adding clear calls here anyway, can we fix it? Just one line to do so, I believe.

editor.setDecorations(cellOptionsSeparatorDecoration, []);
}

// these composite on top of the cell background decoration, so a
// translucent black overlay reads as "slightly darker" in both themes
// (the text is also slightly dimmed to de-emphasize options vs. code)
const cellOptionsBackgroundDecoration = vscode.window.createTextEditorDecorationType({
isWholeLine: true,
opacity: "0.75",
light: {
backgroundColor: "#00000012",
},
dark: {
backgroundColor: "#00000033",
},
});

// the separator is rendered via an "after" attachment (absolutely
// positioned to span the bottom of the row) rather than a border on the
// line itself: vscode applies line decorations to every visual row of a
// soft-wrapped line, which would repeat the border on each wrapped row,
// while an attachment is placed once, after the line's content
const cellOptionsSeparatorDecoration = vscode.window.createTextEditorDecorationType({
isWholeLine: true,
after: {
contentText: "",
textDecoration:
"none; position: absolute; left: 0; bottom: 0; width: 100vw; border-bottom: 1px solid;",
},
light: {
after: {
borderColor: "#00000025",
},
},
dark: {
after: {
borderColor: "#FFFFFF25",
},
},
});

// document lines of the leading run of cell option (#|) comments in a cell
// (same pattern as the tmLanguage rule in ../../syntaxes/build-lang.js:
// optional leading whitespace, then "#|" or "# |")
//
// note: this only handles #-comment languages (r, python, julia, etc.).
// to generalize to all languages (//| for js, --| for sql, /*| ... */
// for c, etc.), derive the prefix from the block's language using
// kLangCommentChars/optionCommentPattern in packages/core/src/jupyter/options.ts
function hashPipeLines(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building on the optionCommentPattern comment I added, the note at lines 256-259 defers //| and --| support, but the pieces already exist and we can bring them in a pretty straightforward way. The block token carries the language (languageNameFromBlock, exported from quarto-core), and kLangCommentChars in cell/options.ts maps each language to its comment prefix. It is private now, so it needs an export. With those two, this function works for js, sql, lua, and ojs cells too, and this third copy of "what is an option line" goes away.

document: vscode.TextDocument,
blockRange: vscode.Range
): number[] {
const lines: number[] = [];
const lastLine = Math.min(blockRange.end.line, document.lineCount - 1);
for (let i = blockRange.start.line + 1; i <= lastLine; i++) {
if (!/^\s*# ?\|/.test(document.lineAt(i).text)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex does not match the same lines as Quarto's own option parser. That parser uses optionCommentPattern in cell/options.ts, which expands to /^#\s*\| ?/ for # languages and accepts any whitespace between # and |.

format.ts already imports optionCommentPattern from cell/options.ts so that its code path cannot drift from the option parser. Can we import it here too, instead of making a third copy of this pattern?

break;
}
lines.push(i);
}
return lines;
}

enum CellBackgroundColor {
Expand Down
7 changes: 7 additions & 0 deletions apps/vscode/syntaxes/build-lang.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@ const fencedCodeBlockDefinition = (
while: (^|\\G)(?!\\s*([\`~]{3,})\\s*$)
contentName: ${contentName}
patterns:
- begin: ^(\\s*)(#\\|)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule matches only #|, but the engine also accepts interior spaces (optionCommentPattern expands to /^#\s*\| ?/), and the decoration in background.ts accepts # |. A line like # | echo: false gets the darker background but no YAML colors. Can we make this pattern match the same lines as optionCommentPattern?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this rule is stamped into every embedded language, but #| is only the option prefix for # comment languages. Cells in other languages use --| (sql, lua), //| (js, ojs, cpp), or %| (matlab) and get no YAML highlighting. The reverse also happens: a literal #| line in a css, json, or yaml block is mis-scoped as active YAML. The generator already emits one definition per language, so we can take the prefix per language (see kLangCommentChars in packages/core/src/jupyter/options.ts).

while: ^(\\s*)(#\\|)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule fires on any #| run in the cell body, not only the leading run, but Quarto only reads options from the leading run, and the new decoration in background.ts stops at the first non-option line. Is it possible for us to handle this the same way Quarto itself does?

```python
x = 1
#| eval: false
```

Maybe not with the declarative TextMate approach, in which case let's just document it.

captures:
'2': {name: 'comment.line.number-sign.quarto'}
contentName: meta.embedded.block.yaml
patterns:
- {include: 'source.yaml'}
${indent(4, scopes)}
`;
};
Expand Down
Loading
Loading