Skip to content

Commit 93d42d9

Browse files
authored
Code Line Numbers Highlighting (#216)
* Add option documentation * Add editor unit tests * Add code highlighting feature * Add release note * Add CSS classes * Documentation tweaks * Polish release notes... * More tweaks
1 parent 9f05fb8 commit 93d42d9

6 files changed

+458
-19
lines changed

_extensions/webr/qwebr-monaco-editor-element.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
// Global array to store Monaco Editor instances
22
globalThis.qwebrEditorInstances = [];
33

4+
function isValidCodeLineNumbers(stringCodeLineNumbers) {
5+
// Regular expression to match valid input strings
6+
const regex = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/;
7+
return regex.test(stringCodeLineNumbers);
8+
}
9+
410
// Function that builds and registers a Monaco Editor instance
511
globalThis.qwebrCreateMonacoEditorInstance = function (cellData) {
612

@@ -54,7 +60,7 @@ globalThis.qwebrCreateMonacoEditorInstance = function (cellData) {
5460
const model = editor.getModel();
5561
// Set EOL for the model
5662
model.setEOL(monaco.editor.EndOfLineSequence.LF);
57-
63+
5864
// Dynamically modify the height of the editor window if new lines are added.
5965
let ignoreEvent = false;
6066
const updateHeight = () => {
@@ -80,6 +86,56 @@ globalThis.qwebrCreateMonacoEditorInstance = function (cellData) {
8086
}
8187
};
8288

89+
// Function to generate decorations to highlight lines
90+
// in the editor based on input string.
91+
function decoratorHighlightLines(codeLineNumbers) {
92+
// Store the lines to be lighlight
93+
let linesToHighlight = [];
94+
95+
// Parse the codeLineNumbers string to get the line numbers to highlight
96+
// First, split the string by commas
97+
codeLineNumbers.split(',').forEach(part => {
98+
// Check if we have a range of lines
99+
if (part.includes('-')) {
100+
// Handle range of lines (e.g., "6-8")
101+
const [start, end] = part.split('-').map(Number);
102+
for (let i = start; i <= end; i++) {
103+
linesToHighlight.push(i);
104+
}
105+
} else {
106+
// Handle single line (e.g., "7")
107+
linesToHighlight.push(Number(part));
108+
}
109+
});
110+
111+
// Create monaco decorations for the lines to highlight
112+
const decorations = linesToHighlight.map(lineNumber => ({
113+
range: new monaco.Range(lineNumber, 1, lineNumber, 1),
114+
options: {
115+
isWholeLine: true,
116+
className: 'qwebr-editor-highlight-line'
117+
}
118+
}));
119+
120+
// Return decorations to be applied to the editor
121+
return decorations;
122+
}
123+
124+
// Ensure that the editor-code-line-numbers option is set and valid
125+
// then apply styling
126+
if (qwebrOptions['editor-code-line-numbers']) {
127+
// Remove all whitespace from the string
128+
const codeLineNumbers = qwebrOptions['editor-code-line-numbers'].replace(/\s/g,'');
129+
// Check if the string is valid for line numbers, e.g., "1,3-5,7"
130+
if (isValidCodeLineNumbers(codeLineNumbers)) {
131+
// Apply the decorations to the editor
132+
editor.createDecorationsCollection(decoratorHighlightLines(codeLineNumbers));
133+
} else {
134+
// Warn the user that the input is invalid
135+
console.warn(`Invalid "editor-code-line-numbers" value in code cell ${qwebrOptions['label']}: ${codeLineNumbers}`);
136+
}
137+
}
138+
83139
// Helper function to check if selected text is empty
84140
function isEmptyCodeText(selectedCodeText) {
85141
return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === "");

_extensions/webr/qwebr-styling.css

+9
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,15 @@ body.quarto-dark .qwebr-button:hover {
116116
color: #696969;
117117
}
118118

119+
/* Style the code highlight lines */
120+
body.quarto-light .qwebr-editor-highlight-line {
121+
background-color: lightblue;
122+
}
123+
124+
body.quarto-dark .qwebr-editor-highlight-line {
125+
background-color: darkblue;
126+
}
127+
119128
/* Style the modal pop-up */
120129

121130
/* The Modal (background) */

0 commit comments

Comments
 (0)