The buffer cursor lives in text coordinates, but the terminal needs a screen position. Today you map the cursor through the scroll offsets and tab expansion to its on-screen row and column, closing the viewport chapter.
Compute the cursor's on-screen position from its buffer position, the scroll offsets, and tab expansion.
Everything the viewport does converges here: the terminal will need to physically
place its cursor, and that position is the buffer cursor pushed through the same two
transforms the text goes through. The screen row is the buffer row minus the row
offset; the screen column is the buffer column’s screen column (tabs expanded)
minus the column offset. On the line "a\tbc", the b at buffer column 2 sits at
screen column 8 because the tab expanded a out to the tab stop.
This is the piece that ties rendering and the cursor together into a coherent view:
the frame from Render and the position from ScreenCursor are computed from the
same offsets and the same tab expansion, so the drawn cursor always lands on the
character it points at, wherever the window has scrolled. That is the entire job of a
viewport - present a movable, tab-aware window onto the buffer and say exactly where
the cursor falls within it - and with it the editor is fully drawable. It reads and
renders; next it needs to load and save real files.
func (e *Editor) ScreenCursor() (int, int) {sr := e.Row - e.rowOffset// renderColumn maps a buffer column to its tab-expanded screen columnsc := renderColumn(e.Buf.Line(e.Row), e.Col) - e.colOffsetreturn sr, sc}