I would not be surprised if somebody proves me wrong, but I’d say this is not currently possible. There’s no hint in the editable DOM elements as to where the cursor is currently active. There’s a selection layer, but it’s completely disconnected from the syntax highlighting elements. Theoretically this can be changed, but it’s not super easy (at least I wouldn’t know without doing some research).
I don’t want to prove you wrong and basically you are right with the current DOM structure it’s not possibe…buuuuuuut with a little LUA/JS/CSS magic everything is possible
I did some tests and this is what me and Gemini could come up with:
Here is the space-style
.sb-attribute[data-completed] {
opacity: 0.25;
transition: opacity 0.2s ease-in-out;
}
.sb-active-line .sb-attribute[data-completed] {
opacity: 1;
}
/*As a bonus, I also added some background to the currently active line, because why not*/
.sb-active-line {
background-color: rgba(0, 170, 255, 0.15) !important;
transition: background-color 0.2s ease;
}
/* Uncoment if you also want opacity:1 while hovering over the line */
/*
.sb-line-li:hover .sb-attribute[data-completed] {
opacity: 1;
}
*/
And here is the space-lua with the JS which modifies the DOM and adds .sb-active-line class to the curent line.
function setupActiveLineHighlighter()
local scriptEl = js.window.document.createElement("script")
scriptEl.innerHTML = [[
(function() {
const CLASS_NAME = "sb-active-line";
let debounceTimer = null;
function performUpdate() {
const cursor = document.querySelector(".cm-cursor-primary");
if (!cursor) return;
const rect = cursor.getBoundingClientRect();
// Offset slightly into the line area to hit the text container
const x = rect.left + 5;
const y = rect.top + (rect.height / 2);
const elementAtCursor = document.elementFromPoint(x, y);
const currentLine = elementAtCursor ? elementAtCursor.closest(".cm-line") : null;
// Remove previous highlights
const oldLines = document.getElementsByClassName(CLASS_NAME);
while(oldLines.length > 0) {
oldLines[0].classList.remove(CLASS_NAME);
}
// Apply new highlight
if (currentLine) {
currentLine.classList.add(CLASS_NAME);
}
}
function updateActiveLine() {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(performUpdate, 50); // <-----here is the debouncer
}
const observer = new MutationObserver((mutations) => {
// Check if the cursor actually changed position/visibility
updateActiveLine();
});
const init = () => {
const scroller = document.querySelector(".cm-scroller");
const cursorLayer = document.querySelector(".cm-cursorLayer");
if (scroller && cursorLayer) {
// Monitor the cursor layer for movement or blinks
observer.observe(cursorLayer, { attributes: true, subtree: true });
// Monitor the content for line changes (Enter/Delete)
const content = document.querySelector(".cm-content");
if (content) {
observer.observe(content, { childList: true });
}
// Handle scrolling and clicking
scroller.addEventListener("scroll", updateActiveLine, { passive: true });
window.addEventListener("click", updateActiveLine);
updateActiveLine();
} else {
// Editor might still be loading
setTimeout(init, 500);
}
};
init();
})();
]]
js.window.document.body.appendChild(scriptEl)
end
event.listen {
name = "editor:pageLoaded",
run = function()
setupActiveLineHighlighter()
end
}
I don’t recommend you doing this. In my opinion is just some unnecessary background processing(although not much, you should not feel any performance drawback from this). i also added a debouncer of 50ms so it doesn’t recalculates on every cursor motion, but waits still the cursor stops for >50ms.
…You asked for it, and I gave you a “solution”.
Captivating! Yet my focus is off the task: by attaching the .sb-active-line class to the current line… we’re able to achieve some “very-responsive” behaviour with 3 lines of css:
```space-style
/* ===========================================================
Base: Smooth transition & visual polish
=========================================================== */
.cm-line {
transition: background-color 0.1s ease-out;
border-radius: 4px; /* Optional: Modern rounded look */
}
/* ===========================================================
L1: Hover state (Lowest priority)
=========================================================== */
.cm-line:hover {
background-color: rgba(65, 90, 115, 0.15);
}
/* ===========================================================
L2: Active cursor line (Medium priority)
Persists as long as the cursor is on the line.
=========================================================== */
.sb-active-line {
background-color: rgba(131, 195, 55, 0.15) !important;
}
/* ===========================================================
L3: Mouse down / Click (Highest priority)
Overrides everything else while the mouse button is held down.
=========================================================== */
/* Case A: Clicking a normal line */
.cm-line:active {
background-color: rgba(255, 165, 0, 0.15) !important;
}
/* Case B: Clicking the ALREADY active line */
/* Slightly darker for better feedback when clicking where you are typing */
.cm-line.sb-active-line:active {
background-color: rgba(255, 165, 0, 0.25) !important;
}
Fun to see you going all out on this! I tried it and I actually quite like it. One thing I noticed: the debouncer causes the highlighted line to flicker when moving the cursor within the current line.
I know, that’s because it recalculates the cursor position(x,y coordinates) with every move with a littl delay and reapplies the class with every move.
if you want here is a version without the debouncer, it still recalculates the positon with every move but does it more agressively, so you can’t see the flicker. choose your poison.
function setupActiveLineHighlighter()
local scriptEl = js.window.document.createElement("script")
scriptEl.innerHTML = [[
(function() {
const CLASS_NAME = "sb-active-line";
function updateActiveLine() {
// 1. Find the primary cursor
const cursor = document.querySelector(".cm-cursor-primary");
if (!cursor) return;
// 2. Get the cursor position
const rect = cursor.getBoundingClientRect();
// We shift slightly to the right to ensure we hit the line text area
const x = rect.left + 5;
const y = rect.top + (rect.height / 2);
// 3. Find the element at that coordinate
const elementAtCursor = document.elementFromPoint(x, y);
const currentLine = elementAtCursor ? elementAtCursor.closest(".cm-line") : null;
// 4. Clean up old highlights
document.querySelectorAll("." + CLASS_NAME).forEach(el => {
if (el !== currentLine) el.classList.remove(CLASS_NAME);
});
// 5. Apply new highlight
if (currentLine && !currentLine.classList.contains(CLASS_NAME)) {
currentLine.classList.add(CLASS_NAME);
}
}
// Observer to watch for cursor movements (changes in style or DOM position)
const observer = new MutationObserver((mutations) => {
updateActiveLine();
});
// We need to wait for the editor to be available in the DOM
const init = () => {
const scroller = document.querySelector(".cm-scroller");
if (scroller) {
// Monitor the cursor layer for changes (blinking/moving)
const cursorLayer = document.querySelector(".cm-cursorLayer");
if (cursorLayer) {
observer.observe(cursorLayer, { attributes: true, subtree: true });
}
// Also update on scrolls and clicks
scroller.addEventListener("scroll", updateActiveLine, { passive: true });
window.addEventListener("click", () => setTimeout(updateActiveLine, 10));
updateActiveLine();
} else {
setTimeout(init, 500);
}
};
init();
})();
]]
js.window.document.body.appendChild(scriptEl)
end
-- Initialize the hack on page load
event.listen {
name = "editor:pageLoaded",
run = function()
setupActiveLineHighlighter()
end
}