-
Notifications
You must be signed in to change notification settings - Fork 62
#| highlighting alternative #1092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f6ac40b
79f8b4c
3da3f82
95bea94
ab3d6d2
bb2fa03
fb04e5d
b91e5e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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 | ||
|
|
@@ -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, []); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
|
@@ -205,6 +300,10 @@ class HiglightingConfig { | |
| return this.enabled_; | ||
| } | ||
|
|
||
| public cellOptionsBackgroundEnabled() { | ||
| return this.cellOptionsBackground_; | ||
| } | ||
|
|
||
| public backgroundDecoration() { | ||
| return this.backgroundDecoration_!; | ||
| } | ||
|
|
@@ -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); | ||
|
|
||
|
|
||
|
|
@@ -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; | ||
|
|
||
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
order: 23is already taken byquarto.cells.hoverHelp.enabled(line 998). Every otherquarto.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.enabled23 → 24signatureHelp.enabled24 → 25diagnostics.enabled25 → 26diagnostics.debounceDelay26 → 27useReticulate27 → 28