Skip to content

Update activecode locked and highlighted region markings on scroll#1190

Merged
bnmnetp merged 1 commit intoRunestoneInteractive:mainfrom
ascholerChemeketa:activecode-lock-display-improvement
Apr 3, 2026
Merged

Update activecode locked and highlighted region markings on scroll#1190
bnmnetp merged 1 commit intoRunestoneInteractive:mainfrom
ascholerChemeketa:activecode-lock-display-improvement

Conversation

@ascholerChemeketa
Copy link
Copy Markdown
Contributor

This pull request improves the handling and rendering of locked and highlighted lines in the ActiveCode interactive editor, especially when scrolling or when only part of the code is visible. The main focus is to ensure that decorations for locked and highlighted lines are applied correctly to only the visible lines, enhancing performance and visual consistency.

Editor rendering and decoration improvements:

  • Added a scroll event listener to the CodeMirror editor to update locked and highlighted regions dynamically as the user scrolls, ensuring offscreen lines are decorated when they come into view.
  • Modified the logic in setHighlightLines to only highlight lines that are currently visible in the editor, using the viewFrom and viewTo properties for accurate line offsets.

Locked line marker placement and decoration:

  • Refactored the locked line marker logic by extracting a placeLock function, and updated its usage to place markers at the correct line numbers.
  • Updated the logic for decorating locked lines at the start and end of the visible regions, ensuring that only lines within the current view are decorated and that locked markers are placed at the midpoint of the locked region. [1] [2]

@ascholerChemeketa
Copy link
Copy Markdown
Contributor Author

Testing advice:
Use:
Paragraph
https://runestone.academy/ns/books/published/PTXSB/activecode.html#activecode-21
and:
Listing 5.2.11: A Python program with preamble/postamble
https://runestone.academy/ns/books/published/PTXSB/activecode.html#activecode-25

Add about 80 lines (blank is fine) between the marked areas, scroll up and down. Currently, the markings disappear when they scroll far enough offscreen.

@bnmnetp
Copy link
Copy Markdown
Member

bnmnetp commented Mar 29, 2026

Not seeing them disappear in Safari, but I'll take your word for it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates the ActiveCode CodeMirror integration so locked-region markers and highlighted lines are re-applied as the editor scrolls, with the intent to only decorate currently rendered/visible lines for better performance and visual consistency.

Changes:

  • Added a CodeMirror scroll listener to re-run locked/highlight decoration logic as the viewport changes.
  • Updated setHighlightLines() to apply highlighting relative to display.viewFrom/viewTo.
  • Refactored locked gutter marker placement into placeLock() and adjusted locked-line decoration to target the current view range.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

editor.display.scrollbars.vert.style.bottom = "16px";
});
});
// Update line makers on scroll. Codemirror only renders a few lines offscreen
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Typo in the new comment: “line makers” should be “line markers” (and “CodeMirror” capitalization would match the rest of the file).

Suggested change
// Update line makers on scroll. Codemirror only renders a few lines offscreen
// Update line markers on scroll. CodeMirror only renders a few lines offscreen

Copilot uses AI. Check for mistakes.
Comment on lines +310 to +315
CodeMirror.on(editor, "scroll", (cm) => {
window.requestAnimationFrame(() => {
this.setLockedRegions();
this.setHighlightLines();
});
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The scroll handler schedules a new requestAnimationFrame for every scroll event; during continuous scrolling this can queue many redundant callbacks in the same frame. Consider debouncing with a stored RAF id (schedule once until it runs) and only re-decorate when the rendered view range actually changed.

Copilot uses AI. Check for mistakes.
Comment on lines +311 to +314
window.requestAnimationFrame(() => {
this.setLockedRegions();
this.setHighlightLines();
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Calling setLockedRegions() on every scroll re-runs markText() for the prefix/suffix each time. CodeMirror creates a new TextMarker per call, so this will accumulate markers and can cause memory/perf issues. Suggest: create/readOnly markers once (store the returned markers and skip if already present, or clear/recreate explicitly) and keep the per-scroll work limited to updating the visible-line decorations/gutter markers.

Copilot uses AI. Check for mistakes.
Comment on lines 459 to 465
let lineNum = line.split("-");
if (lineNum.length > 1) {
for (let i = parseInt(lineNum[0]); i <= parseInt(lineNum[1]); i++) {
let startLine = parseInt(lineNum[0]) - this.editor.display.viewFrom;
let endLine = parseInt(lineNum[1]) - this.editor.display.viewFrom;
for (let i = startLine; i <= endLine; i++) {
lines[i - 1].classList.add("CodeMirror__highlight-line");
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Range highlighting can compute startLine/endLine outside the current rendered view (e.g., when the highlighted range is offscreen). The loop then calls lines[i - 1].classList on undefined for negative/too-large indices, which will throw. Clamp the range to the visible window (e.g., max(viewFrom+1, start) .. min(viewTo, end)) and guard against missing DOM nodes before adding the class.

Copilot uses AI. Check for mistakes.
Comment on lines +504 to +537
@@ -509,7 +530,11 @@ export class ActiveCode extends RunestoneBase {
this.editor.doc.getValue().length - this.visibleSuffixLength;
let endPos = this.editor.posFromIndex(endIndex);
let lastLine = this.editor.doc.lastLine();
decorateLines.call(this, endPos.line, lastLine);
placeLock.call(this, Math.floor((endPos.line + lastLine) / 2));

let startLine = Math.max(endPos.line - startViewRange, 0);
let endLine = Math.min(lastLine - startViewRange, endViewRange);
decorateLines.call(this, startLine, endLine);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Visible-range math for locked-line decoration mixes absolute doc line numbers (viewTo) with DOM-relative indices (0..lines.length-1). For example endLine uses Math.min(lastLine - viewFrom, viewTo) which can greatly exceed the rendered line count when scrolled, causing large unnecessary loops each scroll and risking incorrect decoration bounds. Compute the max index using (viewTo - viewFrom) (and confirm whether viewTo is exclusive), then clamp start/end to [0, renderedCount-1].

Copilot uses AI. Check for mistakes.
@ascholerChemeketa ascholerChemeketa force-pushed the activecode-lock-display-improvement branch from 9897e2c to 3d57405 Compare April 3, 2026 20:54
@ascholerChemeketa
Copy link
Copy Markdown
Contributor Author

Went back to the drawing board and had ChatGPT help we figure out a better way to address the slow down issue that the custom line decoration was avoiding. The CodeMirror built in methods for adding/removing classes are pretty inefficient - they completely remove the DOM element for lines and replace them with new ones.

But, as long as they are batched using this.editor.operation(() => {}); the CodeMirror, the delay isn't awful.

So this force push now has a refactor of that logic to simplify things. No more manual decorations and trying to update on scroll.

@bnmnetp bnmnetp merged commit e3564f2 into RunestoneInteractive:main Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants