How to detect active "line" within soft wrapped line

How to detect the boundary of the current “line” (with cursor) in a long line that is soft wrapped multiple times?
(To modify the addon “selection/active-line” to highlight only that line fragment).

Edit (after Google research): Or rather, does CM have anything that would help with that detection?

You can use moveToLineBoundary to detect wrap points in a line.

1 Like

Is there an equivalent for the current CM version (5.x)?
Or maybe it’s relatively easy to backport the detection from moveToLineBoundary code to 5.x?

Oh, right, no, I don’t think 5.x provides something like that directly, but I think calling coordsChar with position with the line’s vertical position and horizontal positions to the left/right of the editor should work.

I made some progress (I was able to detect the first “line” of wrapped line).

But how do I style that line fragment? Classes for spans from markText are overridden by other span (or line) classes inserted later by the mode, so simple
cm.doc.markText(range.from,range.to, {className:WRAP_CLASS+" "+BACK_CLASS})
doesn’t color the fragment (code placed in the addon active-line function updateActiveLines).

Are they? Aren’t they just combined together?

They are, but if a later class (on the same span) have the same attribute, it will overwrite the previous setting (unless it had !important).

I tried it again and the active classes are working on non-tokenized line, but if the line is a token that sets background-color (to rgba(34,17,153,0.04), if it have any meaning), then it disables the background-color of the added .CodeMirror-activeline-background (I assume the token classes are added after setting the “active” classes - token classes are listed as the first ones in the span class list in F12 Inspector).

Other questions:

  • Can something else be used to mark (color) the fragment beside the region mark (I clear all marks to “unfold all”, so it probably will conflict)?
  • Is there a relatively simple way (without changing the CM code) to mark the entire “line” (to the right border of the editor window)? I can mark only text in line, so there will be gaps on the right - especially at the end of the line, where there could be only one word.

I think that addLineWidget could be the answer, depending on how it works (didn’t tested it yet).

Oh, by the way: how to mark the range marks, to filter them out from the getAllMarks list (add custom property, change type or something else)?

Answer to “how to mark the range marks” - obviously by filtering by the classes in className (in this case).

There’s a cm.cursorCoords and cm.doc.sel.ranges discrepancy - cursor placed by mouse right at the start (before the first char) of line 19:

cm.cursorCoords(true,"page").top
446.6999969482422
cm.cursorCoords(false,"page").top
446.6999969482422
cm.getCursor()
Object { line: 18, ch: 2, sticky: "before", xRel: 20.699996948242188 }
cm.cursorCoords(cm.getCursor(),"page").top
446.6999969482422

sel.range.head
Object { line: 19, ch: 0, sticky: "after", xRel: 1.6999969482421875 }
cm.cursorCoords(range.head,"page").top
466.1999969482422
cm.cursorCoords({line:range.head.line,ch:0,sticky:"before"},"page").top
466.1999969482422

Hmm… This could be a CM or custom implementation of CM (5.58.3) fault, because it handles Home/End on wrapped lines in a weird way (End [goLineRight] places cursor before the first char in next “line” [position gets sticky=before]).

Well, I finally made it.

Notes to self:

  • if mode sets background color for tokens, the BACK_CLASS will be overriden (maybe add !important to it?)

Code for wrap detection:

// somewhere in updateActiveLines (active-line addon)
const selTop=cm.cursorCoords(range.head,"page").top;
const startTop=cm.cursorCoords({line:range.head.line,ch:0},"page").top;
const endTop=cm.cursorCoords({line:range.head.line,ch:line.length},"page").top;
if (startTop-endTop!=0)
{
	wrapLine.top=selTop;
	wrapLine.pos=range.head;
	wrapLine.line=line;
}

active.push(wrapLine.line?wrapLine:line);

//...

//parts in reverse order for readability
const range=activeLineRange(active[i].pos,active[i].top,active[i].line.text.length);
const mark=cm.doc.markText(range.from,range.to, {className:WRAP_CLASS+" "+BACK_CLASS});

function activeLineRange(cursorPos,top,maxCh)
{
	const wrapLength=Math.round(/*0.91**/(cm.display.lineDiv.offsetWidth/cm.defaultCharWidth()));

	function findWrap(pos,top,dir,max)
	{
		const newPos=Object.assign({},pos);
		if (newPos.sticky=="before")
		{
			if (dir<0)
				newPos.sticky="after";
		}
		else if (dir>0)
			newPos.sticky="before";


		function approximate(newPos,top,dir,step,max)
		{
			const tmp=Object.assign({},newPos);
			step=Math.round(step/2);
			tmp.ch+=dir*step;

			const tmpTop=cm.cursorCoords(tmp,"page").top;

			if (tmpTop==top)
			{
				if (tmp.ch<=0)
					return 0;
				else if (tmp.ch>=max)
					return max;

				step*=2;
			}
			else
			{
				if (step>1)
					tmp.ch-=dir*step;
				else
				{
					tmp.ch-=dir;
					return tmp.ch;
				}
			}

			return approximate(tmp,top,dir,step,max);
		}
					
		newPos.ch=approximate(newPos,top,dir,wrapLength,maxCh);

		return newPos;
	}

	const from=findWrap(cursorPos,top,-1);
	const to=findWrap(cursorPos,top,1,maxCh);
			  	
	return {from:from,to:to};			  	
}