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
1 change: 1 addition & 0 deletions apps/vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Reduce memory usage by only starting the language server (LSP) in projects containing Quarto documents (https://github.com/quarto-dev/quarto/pull/1059).
- Fixed a bug where single-line display math with a cross-reference label (e.g. `$$1+1$$ {#eq-spec0}`), or an unclosed `$$`, stopped the rest of the document from being parsed, so headings went missing from the outline, LaTeX preview was unavailable, and code cells below could not be run (<https://github.com/quarto-dev/quarto/pull/1063>).
- Add highlighting for option comments in code cells (<https://github.com/quarto-dev/quarto/pull/1084>).

## 1.135.0 (Release on 2026-07-08)

Expand Down
19 changes: 13 additions & 6 deletions apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -995,28 +995,28 @@
]
},
"quarto.cells.hoverHelp.enabled": {
"order": 23,
"order": 24,
"scope": "window",
"type": "boolean",
"default": true,
"markdownDescription": "Show help when hovering over functions."
},
"quarto.cells.signatureHelp.enabled": {
"order": 24,
"order": 25,
"scope": "window",
"type": "boolean",
"default": true,
"markdownDescription": "Show parameter help when editing function calls."
},
"quarto.cells.diagnostics.enabled": {
"order": 25,
"order": 26,
"scope": "window",
"type": "boolean",
"default": true,
"markdownDescription": "Enable diagnostics (linting) for code blocks from language servers."
},
"quarto.cells.diagnostics.debounceDelay": {
"order": 26,
"order": 27,
"scope": "window",
"type": "number",
"default": 500,
Expand Down Expand Up @@ -1067,8 +1067,15 @@
"default": 250,
"markdownDescription": "Millisecond delay between background color updates."
},
"quarto.cells.options.background": {
"order": 23,

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.

order: 23 is already taken by quarto.cells.hoverHelp.enabled (line 998). Every other quarto.cells.* setting has a unique order. With the tie, this one interleaves with the hover-help group in the Settings UI instead of the background settings.

The background group runs 19 to 22, so 23 is the right slot for this setting. Can we shift the other ones:

  • hoverHelp.enabled 23 → 24
  • signatureHelp.enabled 24 → 25
  • diagnostics.enabled 25 → 26
  • diagnostics.debounceDelay 26 → 27
  • useReticulate 27 → 28

"scope": "window",
"type": "boolean",
"default": true,
"markdownDescription": "Apply a visual treatment to cell option comments such as `#|` lines: a slightly darker background, dimmed text, and a separator below the options. When `#quarto.cells.background.color#` is `off`, this setting has no effect."
},
"quarto.cells.useReticulate": {
"order": 27,
"order": 28,
"scope": "window",
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -1495,7 +1502,7 @@
"build": "tsx build.ts",
"dev": "yarn run build dev",
"lint": "eslint src --ext ts",
"build-lang": "node syntaxes/build-lang",
"build-lang": "tsx syntaxes/build-lang.js",
"build-test": "yarn run build test",
"test": "yarn build-test && vscode-test",
"test-positron": "yarn build-test && node ./scripts/run-positron-tests.mjs"
Expand Down
105 changes: 103 additions & 2 deletions apps/vscode/src/providers/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import * as vscode from "vscode";

import { isQuartoDoc, kQuartoDocSelector } from "../core/doc";
import { MarkdownEngine } from "../markdown/engine";
import { isExecutableLanguageBlock } from "quarto-core";
import { isExecutableLanguageBlock, languageNameFromBlock } from "quarto-core";
import { vscRange } from "../core/range";
import { createThrottle } from "../core/throttle";
import { langCommentChars, optionCommentPattern } from "./cell/comment-chars";

export function activateBackgroundHighlighter(
context: vscode.ExtensionContext,
Expand Down Expand Up @@ -153,13 +154,34 @@ 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)
if (highlightingConfig.cellOptionsBackgroundEnabled()) {
const lines = cellOptionLines(
editor.document,
blockRange,
languageNameFromBlock(block)
);
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 +208,83 @@ 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(highlightingConfig.inlineBackgroundDecoration(), []);
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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

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
// (#| for python/r, //| for js, etc. -- the same pattern used by the
// tmLanguage rules generated in ../../syntaxes/build-lang.js, with
// optional leading indentation allowed)
//
// note: block-comment languages (e.g. /*| ... */ for c and css) are not
// supported (same as the tmLanguage)
function cellOptionLines(
document: vscode.TextDocument,
blockRange: vscode.Range,
language: string
): number[] {
const commentChars = langCommentChars(language);
if (commentChars.length > 1) {
return [];
}
const pattern = new RegExp(
"^\\s*" + optionCommentPattern(commentChars[0]).source.replace(/^\^/, "")
);
const lines: number[] = [];
const lastLine = Math.min(blockRange.end.line, document.lineCount - 1);
for (let i = blockRange.start.line + 1; i <= lastLine; i++) {
if (!pattern.test(document.lineAt(i).text)) {
break;
}
lines.push(i);
}
return lines;
}

enum CellBackgroundColor {
Expand All @@ -205,6 +300,10 @@ class HiglightingConfig {
return this.enabled_;
}

public cellOptionsBackgroundEnabled() {
return this.cellOptionsBackground_;
}

public backgroundDecoration() {
return this.backgroundDecoration_!;
}
Expand All @@ -231,6 +330,7 @@ class HiglightingConfig {
}

this.enabled_ = backgroundOption !== CellBackgroundColor.off;
this.cellOptionsBackground_ = config.get<boolean>("cells.options.background", true);
this.delayMs_ = config.get("cells.background.delay", 250);


Expand Down Expand Up @@ -262,6 +362,7 @@ class HiglightingConfig {
}

private enabled_ = true;
private cellOptionsBackground_ = true;
private backgroundDecoration_: vscode.TextEditorDecorationType | undefined;
private inlineBackgroundDecoration_: vscode.TextEditorDecorationType | undefined;
private delayMs_ = 250;
Expand Down
85 changes: 85 additions & 0 deletions apps/vscode/src/providers/cell/comment-chars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* comment-chars.ts
*
* Copyright (C) 2026 by Posit Software, PBC
*
* Unless you have received this program directly from Posit Software pursuant
* to the terms of a commercial license agreement with Posit Software, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/

// comment characters used for cell options by language (e.g. #| for
// python/r, //| for js, --| for sql). pairs are block comment delimiters
// (/*| ... */).
//
// note: this module must remain dependency-free: it is also imported by
// syntaxes/build-lang.js to generate the cell option comment rules of the
// quarto textmate grammar

export const kLangCommentChars: Record<string, string | [string, string]> = {
r: "#",
python: "#",
julia: "#",
scala: "//",
matlab: "%",
csharp: "//",
fsharp: "//",
c: ["/*", "*/"],
css: ["/*", "*/"],
sas: ["*", ";"],
powershell: "#",
bash: "#",
sql: "--",
mysql: "--",
psql: "--",
lua: "--",
cpp: "//",
cc: "//",
stan: "#",
octave: "#",
fortran: "!",
fortran95: "!",
awk: "#",
gawk: "#",
stata: "*",
java: "//",
groovy: "//",
sed: "#",
perl: "#",
ruby: "#",
tikz: "%",
js: "//",
d3: "//",
node: "//",
sass: "//",
coffee: "#",
go: "//",
asy: "//",
haskell: "--",
dot: "//",
mermaid: "%%",
ojs: "//",
apl: "⍝",
};

export function langCommentChars(lang: string): string[] {
const chars = kLangCommentChars[lang] || "#";
if (!Array.isArray(chars)) {
return [chars];
} else {
return chars;
}
}

export function optionCommentPattern(comment: string) {
return new RegExp("^" + escapeRegExp(comment) + "\\s*\\| ?");
}

function escapeRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
64 changes: 4 additions & 60 deletions apps/vscode/src/providers/cell/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import * as yaml from "js-yaml";
import { lines } from "core";
import { Token, isCodeBlock, languageNameFromBlock } from "quarto-core";

import { langCommentChars, optionCommentPattern } from "./comment-chars";

export { optionCommentPattern } from "./comment-chars";


export const kExecuteEval = "eval";

Expand Down Expand Up @@ -83,63 +87,3 @@ export function cellOptions(language: string, source: string[]): Record<string,
}
}

function langCommentChars(lang: string): string[] {
const chars = kLangCommentChars[lang] || "#";
if (!Array.isArray(chars)) {
return [chars];
} else {
return chars;
}
}
export function optionCommentPattern(comment: string) {
return new RegExp("^" + escapeRegExp(comment) + "\\s*\\| ?");
}

const kLangCommentChars: Record<string, string | [string, string]> = {
r: "#",
python: "#",
julia: "#",
scala: "//",
matlab: "%",
csharp: "//",
fsharp: "//",
c: ["/*", "*/"],
css: ["/*", "*/"],
sas: ["*", ";"],
powershell: "#",
bash: "#",
sql: "--",
mysql: "--",
psql: "--",
lua: "--",
cpp: "//",
cc: "//",
stan: "#",
octave: "#",
fortran: "!",
fortran95: "!",
awk: "#",
gawk: "#",
stata: "*",
java: "//",
groovy: "//",
sed: "#",
perl: "#",
ruby: "#",
tikz: "%",
js: "//",
d3: "//",
node: "//",
sass: "//",
coffee: "#",
go: "//",
asy: "//",
haskell: "--",
dot: "//",
ojs: "//",
apl: "⍝",
};

function escapeRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
Loading
Loading