TextArea now supports actual tab characters. Added additional data cached to improve performance.
This commit is contained in:
parent
2a2a386592
commit
7ec2aead8f
1 changed files with 302 additions and 83 deletions
|
|
@ -98,6 +98,20 @@ typedef struct {
|
||||||
int32_t undoCursor;
|
int32_t undoCursor;
|
||||||
int32_t cachedLines;
|
int32_t cachedLines;
|
||||||
int32_t cachedMaxLL;
|
int32_t cachedMaxLL;
|
||||||
|
|
||||||
|
// Line offset cache: lineOffsets[i] = byte offset of start of line i.
|
||||||
|
// lineOffsets[lineCount] = past-end sentinel (len or len+1).
|
||||||
|
// Rebuilt lazily when cachedLines == -1.
|
||||||
|
int32_t *lineOffsets;
|
||||||
|
int32_t lineOffsetCap;
|
||||||
|
|
||||||
|
// Per-line visual length cache (tab-expanded). -1 = dirty.
|
||||||
|
int32_t *lineVisLens;
|
||||||
|
int32_t lineVisLenCap;
|
||||||
|
|
||||||
|
// Cached cursor byte offset (avoids O(N) recomputation per keystroke)
|
||||||
|
int32_t cursorOff;
|
||||||
|
|
||||||
int32_t sbDragOrient;
|
int32_t sbDragOrient;
|
||||||
int32_t sbDragOff;
|
int32_t sbDragOff;
|
||||||
bool sbDragging;
|
bool sbDragging;
|
||||||
|
|
@ -159,17 +173,21 @@ static bool maskIsSlot(char ch);
|
||||||
static int32_t maskNextSlot(const char *mask, int32_t pos);
|
static int32_t maskNextSlot(const char *mask, int32_t pos);
|
||||||
static int32_t maskPrevSlot(const char *mask, int32_t pos);
|
static int32_t maskPrevSlot(const char *mask, int32_t pos);
|
||||||
static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
static int32_t textAreaCountLines(const char *buf, int32_t len);
|
|
||||||
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col);
|
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col);
|
||||||
static inline void textAreaDirtyCache(WidgetT *w);
|
static inline void textAreaDirtyCache(WidgetT *w);
|
||||||
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols);
|
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols);
|
||||||
static int32_t textAreaGetLineCount(WidgetT *w);
|
static int32_t textAreaGetLineCount(WidgetT *w);
|
||||||
static int32_t textAreaGutterWidth(WidgetT *w, const BitmapFontT *font);
|
static int32_t textAreaGutterWidth(WidgetT *w, const BitmapFontT *font);
|
||||||
static int32_t textAreaGetMaxLineLen(WidgetT *w);
|
static int32_t textAreaGetMaxLineLen(WidgetT *w);
|
||||||
|
static int32_t textAreaLineLenCached(WidgetT *w, int32_t row);
|
||||||
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row);
|
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row);
|
||||||
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row);
|
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row);
|
||||||
static int32_t textAreaMaxLineLen(const char *buf, int32_t len);
|
static int32_t textAreaLineStartCached(WidgetT *w, int32_t row);
|
||||||
|
static void textAreaRebuildCache(WidgetT *w);
|
||||||
static void textAreaOffToRowCol(const char *buf, int32_t off, int32_t *row, int32_t *col);
|
static void textAreaOffToRowCol(const char *buf, int32_t off, int32_t *row, int32_t *col);
|
||||||
|
static int32_t visualCol(const char *buf, int32_t lineStart, int32_t off, int32_t tabW);
|
||||||
|
static int32_t visualColToOff(const char *buf, int32_t len, int32_t lineStart, int32_t targetVC, int32_t tabW);
|
||||||
|
static int32_t visualLineLen(const char *buf, int32_t len, int32_t lineStart, int32_t tabW);
|
||||||
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
|
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
|
||||||
static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
static void widgetTextAreaOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
static void widgetTextAreaOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||||
|
|
@ -615,31 +633,97 @@ done:
|
||||||
// TextArea line helpers
|
// TextArea line helpers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static int32_t textAreaCountLines(const char *buf, int32_t len) {
|
// ============================================================
|
||||||
int32_t lines = 1;
|
// Line offset cache
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// lineOffsets[i] = byte offset where line i starts.
|
||||||
|
// lineOffsets[lineCount] = len (sentinel past last line).
|
||||||
|
// Built lazily on first access after invalidation (cachedLines == -1).
|
||||||
|
// Single O(N) scan builds both the line offset table and per-line
|
||||||
|
// visual length cache simultaneously.
|
||||||
|
|
||||||
|
static void textAreaRebuildCache(WidgetT *w) {
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
char *buf = ta->buf;
|
||||||
|
int32_t len = ta->len;
|
||||||
|
int32_t tabW = ta->tabWidth > 0 ? ta->tabWidth : 3;
|
||||||
|
|
||||||
|
// Count lines first
|
||||||
|
int32_t lineCount = 1;
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
for (int32_t i = 0; i < len; i++) {
|
||||||
if (buf[i] == '\n') {
|
if (buf[i] == '\n') {
|
||||||
lines++;
|
lineCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
// Grow line offset array if needed (+1 for sentinel)
|
||||||
|
int32_t needed = lineCount + 1;
|
||||||
|
|
||||||
|
if (needed > ta->lineOffsetCap) {
|
||||||
|
int32_t newCap = needed + 256;
|
||||||
|
ta->lineOffsets = (int32_t *)realloc(ta->lineOffsets, newCap * sizeof(int32_t));
|
||||||
|
ta->lineOffsetCap = newCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow visual length array if needed
|
||||||
|
if (lineCount > ta->lineVisLenCap) {
|
||||||
|
int32_t newCap = lineCount + 256;
|
||||||
|
ta->lineVisLens = (int32_t *)realloc(ta->lineVisLens, newCap * sizeof(int32_t));
|
||||||
|
ta->lineVisLenCap = newCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single pass: record offsets and compute visual lengths
|
||||||
|
int32_t line = 0;
|
||||||
|
int32_t vc = 0;
|
||||||
|
int32_t maxVL = 0;
|
||||||
|
|
||||||
|
ta->lineOffsets[0] = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < len; i++) {
|
||||||
|
if (buf[i] == '\n') {
|
||||||
|
ta->lineVisLens[line] = vc;
|
||||||
|
|
||||||
|
if (vc > maxVL) {
|
||||||
|
maxVL = vc;
|
||||||
|
}
|
||||||
|
|
||||||
|
line++;
|
||||||
|
ta->lineOffsets[line] = i + 1;
|
||||||
|
vc = 0;
|
||||||
|
} else if (buf[i] == '\t') {
|
||||||
|
vc += tabW - (vc % tabW);
|
||||||
|
} else {
|
||||||
|
vc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last line (may not end with newline)
|
||||||
|
ta->lineVisLens[line] = vc;
|
||||||
|
|
||||||
|
if (vc > maxVL) {
|
||||||
|
maxVL = vc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ta->lineOffsets[lineCount] = len;
|
||||||
|
ta->cachedLines = lineCount;
|
||||||
|
ta->cachedMaxLL = maxVL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Cached line count -- sentinel value -1 means "dirty, recompute".
|
static void textAreaEnsureCache(WidgetT *w) {
|
||||||
// This avoids O(N) buffer scans on every paint frame. The cache is
|
|
||||||
// invalidated (set to -1) by textAreaDirtyCache() after any buffer
|
|
||||||
// mutation. The same pattern is used for max line length.
|
|
||||||
static int32_t textAreaGetLineCount(WidgetT *w) {
|
|
||||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
|
||||||
if (ta->cachedLines < 0) {
|
if (ta->cachedLines < 0) {
|
||||||
ta->cachedLines = textAreaCountLines(ta->buf, ta->len);
|
textAreaRebuildCache(w);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ta->cachedLines;
|
|
||||||
|
static int32_t textAreaGetLineCount(WidgetT *w) {
|
||||||
|
textAreaEnsureCache(w);
|
||||||
|
return ((TextAreaDataT *)w->data)->cachedLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -668,7 +752,11 @@ static int32_t textAreaGutterWidth(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) {
|
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) {
|
||||||
|
(void)buf;
|
||||||
(void)len;
|
(void)len;
|
||||||
|
(void)row;
|
||||||
|
// Should not be called — use textAreaLineStartCached() instead.
|
||||||
|
// Fallback: linear scan (only reachable if cache isn't built yet)
|
||||||
int32_t off = 0;
|
int32_t off = 0;
|
||||||
|
|
||||||
for (int32_t r = 0; r < row; r++) {
|
for (int32_t r = 0; r < row; r++) {
|
||||||
|
|
@ -685,6 +773,44 @@ static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// O(1) line start via cache
|
||||||
|
static int32_t textAreaLineStartCached(WidgetT *w, int32_t row) {
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
textAreaEnsureCache(w);
|
||||||
|
|
||||||
|
if (row < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row >= ta->cachedLines) {
|
||||||
|
return ta->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ta->lineOffsets[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// O(1) line length via cache
|
||||||
|
static int32_t textAreaLineLenCached(WidgetT *w, int32_t row) {
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
textAreaEnsureCache(w);
|
||||||
|
|
||||||
|
if (row < 0 || row >= ta->cachedLines) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t start = ta->lineOffsets[row];
|
||||||
|
int32_t next = ta->lineOffsets[row + 1];
|
||||||
|
|
||||||
|
// Subtract newline if present
|
||||||
|
if (next > start && ta->buf[next - 1] == '\n') {
|
||||||
|
next--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row) {
|
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row) {
|
||||||
int32_t start = textAreaLineStart(buf, len, row);
|
int32_t start = textAreaLineStart(buf, len, row);
|
||||||
int32_t end = start;
|
int32_t end = start;
|
||||||
|
|
@ -697,37 +823,9 @@ static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaMaxLineLen(const char *buf, int32_t len) {
|
|
||||||
int32_t maxLen = 0;
|
|
||||||
int32_t curLen = 0;
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
|
||||||
if (buf[i] == '\n') {
|
|
||||||
if (curLen > maxLen) {
|
|
||||||
maxLen = curLen;
|
|
||||||
}
|
|
||||||
curLen = 0;
|
|
||||||
} else {
|
|
||||||
curLen++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curLen > maxLen) {
|
|
||||||
maxLen = curLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaGetMaxLineLen(WidgetT *w) {
|
static int32_t textAreaGetMaxLineLen(WidgetT *w) {
|
||||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
textAreaEnsureCache(w);
|
||||||
|
return ((TextAreaDataT *)w->data)->cachedMaxLL;
|
||||||
if (ta->cachedMaxLL < 0) {
|
|
||||||
ta->cachedMaxLL = textAreaMaxLineLen(ta->buf, ta->len);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ta->cachedMaxLL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -738,6 +836,66 @@ static inline void textAreaDirtyCache(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Tab-aware visual column from buffer offset within a line.
|
||||||
|
// tabW <= 0 means tabs are 1 column (no expansion).
|
||||||
|
static int32_t visualCol(const char *buf, int32_t lineStart, int32_t off, int32_t tabW) {
|
||||||
|
int32_t vc = 0;
|
||||||
|
|
||||||
|
for (int32_t i = lineStart; i < off; i++) {
|
||||||
|
if (buf[i] == '\t' && tabW > 0) {
|
||||||
|
vc += tabW - (vc % tabW);
|
||||||
|
} else {
|
||||||
|
vc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Tab-aware: convert visual column to buffer offset within a line.
|
||||||
|
static int32_t visualColToOff(const char *buf, int32_t len, int32_t lineStart, int32_t targetVC, int32_t tabW) {
|
||||||
|
int32_t vc = 0;
|
||||||
|
int32_t i = lineStart;
|
||||||
|
|
||||||
|
while (i < len && buf[i] != '\n') {
|
||||||
|
int32_t w = 1;
|
||||||
|
|
||||||
|
if (buf[i] == '\t' && tabW > 0) {
|
||||||
|
w = tabW - (vc % tabW);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vc + w > targetVC) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vc += w;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Tab-aware visual line length (in visual columns).
|
||||||
|
static int32_t visualLineLen(const char *buf, int32_t len, int32_t lineStart, int32_t tabW) {
|
||||||
|
int32_t vc = 0;
|
||||||
|
int32_t i = lineStart;
|
||||||
|
|
||||||
|
while (i < len && buf[i] != '\n') {
|
||||||
|
if (buf[i] == '\t' && tabW > 0) {
|
||||||
|
vc += tabW - (vc % tabW);
|
||||||
|
} else {
|
||||||
|
vc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col) {
|
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col) {
|
||||||
int32_t start = textAreaLineStart(buf, len, row);
|
int32_t start = textAreaLineStart(buf, len, row);
|
||||||
int32_t lineL = textAreaLineLen(buf, len, row);
|
int32_t lineL = textAreaLineLen(buf, len, row);
|
||||||
|
|
@ -768,7 +926,16 @@ static void textAreaOffToRowCol(const char *buf, int32_t off, int32_t *row, int3
|
||||||
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols) {
|
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols) {
|
||||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
int32_t row = ta->cursorRow;
|
int32_t row = ta->cursorRow;
|
||||||
int32_t col = ta->cursorCol;
|
|
||||||
|
// Use cached cursor offset to avoid O(N) recomputation
|
||||||
|
int32_t curLineOff = textAreaLineStartCached(w, row);
|
||||||
|
int32_t curOff = curLineOff + ta->cursorCol;
|
||||||
|
|
||||||
|
if (curOff > ta->len) {
|
||||||
|
curOff = ta->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t col = visualCol(ta->buf, curLineOff, curOff, ta->tabWidth);
|
||||||
|
|
||||||
if (row < ta->scrollRow) {
|
if (row < ta->scrollRow) {
|
||||||
ta->scrollRow = row;
|
ta->scrollRow = row;
|
||||||
|
|
@ -808,6 +975,8 @@ void widgetTextAreaDestroy(WidgetT *w) {
|
||||||
if (ta) {
|
if (ta) {
|
||||||
free(ta->buf);
|
free(ta->buf);
|
||||||
free(ta->undoBuf);
|
free(ta->undoBuf);
|
||||||
|
free(ta->lineOffsets);
|
||||||
|
free(ta->lineVisLens);
|
||||||
free(ta);
|
free(ta);
|
||||||
w->data = NULL;
|
w->data = NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -1606,8 +1775,8 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
int32_t relX = vx - innerX;
|
int32_t relX = vx - innerX;
|
||||||
int32_t relY = vy - innerY;
|
int32_t relY = vy - innerY;
|
||||||
|
|
||||||
int32_t clickRow = ta->scrollRow + relY / font->charHeight;
|
int32_t clickRow = ta->scrollRow + relY / font->charHeight;
|
||||||
int32_t clickCol = ta->scrollCol + relX / font->charWidth;
|
int32_t clickVisCol = ta->scrollCol + relX / font->charWidth;
|
||||||
|
|
||||||
if (clickRow < 0) {
|
if (clickRow < 0) {
|
||||||
clickRow = 0;
|
clickRow = 0;
|
||||||
|
|
@ -1617,11 +1786,14 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
clickRow = totalLines - 1;
|
clickRow = totalLines - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clickCol < 0) {
|
if (clickVisCol < 0) {
|
||||||
clickCol = 0;
|
clickVisCol = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t lineL = textAreaLineLen(ta->buf, ta->len, clickRow);
|
int32_t clkLineStart = textAreaLineStartCached(w, clickRow);
|
||||||
|
int32_t clkByteOff = visualColToOff(ta->buf, ta->len, clkLineStart, clickVisCol, ta->tabWidth);
|
||||||
|
int32_t clickCol = clkByteOff - clkLineStart;
|
||||||
|
int32_t lineL = textAreaLineLenCached(w, clickRow);
|
||||||
|
|
||||||
if (clickCol > lineL) {
|
if (clickCol > lineL) {
|
||||||
clickCol = lineL;
|
clickCol = lineL;
|
||||||
|
|
@ -1792,7 +1964,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
int32_t gutterX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
int32_t gutterX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
||||||
int32_t textX = gutterX + gutterW;
|
int32_t textX = gutterX + gutterW;
|
||||||
int32_t textY = w->y + TEXTAREA_BORDER;
|
int32_t textY = w->y + TEXTAREA_BORDER;
|
||||||
int32_t lineOff = textAreaLineStart(buf, len, ta->scrollRow);
|
int32_t lineOff = textAreaLineStartCached(w, ta->scrollRow);
|
||||||
|
|
||||||
// Draw gutter background
|
// Draw gutter background
|
||||||
if (gutterW > 0) {
|
if (gutterW > 0) {
|
||||||
|
|
@ -1858,36 +2030,74 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
uint32_t savedBg = bg;
|
uint32_t savedBg = bg;
|
||||||
bg = lineBg;
|
bg = lineBg;
|
||||||
|
|
||||||
// Visible range within line
|
// Expand tabs in this line into a temporary buffer for drawing.
|
||||||
int32_t scrollCol = ta->scrollCol;
|
// Also expand syntax colors so each visual column has a color byte.
|
||||||
int32_t visStart = scrollCol;
|
int32_t tabW = ta->tabWidth > 0 ? ta->tabWidth : 3;
|
||||||
int32_t visEnd = scrollCol + visCols;
|
|
||||||
int32_t textEnd = lineL; // chars in this line
|
|
||||||
|
|
||||||
// Clamp visible range to actual line content for text drawing
|
// Compute syntax colors for the raw line first (before expansion)
|
||||||
int32_t drawStart = visStart < textEnd ? visStart : textEnd;
|
uint8_t rawSyntax[MAX_COLORIZE_LEN];
|
||||||
int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd;
|
|
||||||
|
|
||||||
// Compute syntax colors for this line if colorizer is set
|
|
||||||
uint8_t syntaxBuf[MAX_COLORIZE_LEN];
|
|
||||||
bool hasSyntax = false;
|
bool hasSyntax = false;
|
||||||
|
|
||||||
if (ta->colorize && lineL > 0) {
|
if (ta->colorize && lineL > 0) {
|
||||||
int32_t colorLen = lineL < MAX_COLORIZE_LEN ? lineL : MAX_COLORIZE_LEN;
|
int32_t colorLen = lineL < MAX_COLORIZE_LEN ? lineL : MAX_COLORIZE_LEN;
|
||||||
memset(syntaxBuf, 0, colorLen);
|
memset(rawSyntax, 0, colorLen);
|
||||||
ta->colorize(buf + lineOff, colorLen, syntaxBuf, ta->colorizeCtx);
|
ta->colorize(buf + lineOff, colorLen, rawSyntax, ta->colorizeCtx);
|
||||||
hasSyntax = true;
|
hasSyntax = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine selection intersection with this line
|
// Expand tabs: build visual text and expanded syntax buffers
|
||||||
|
char expandBuf[MAX_COLORIZE_LEN];
|
||||||
|
uint8_t syntaxBuf[MAX_COLORIZE_LEN];
|
||||||
|
int32_t expandLen = 0;
|
||||||
|
int32_t vc = 0;
|
||||||
|
|
||||||
|
for (int32_t j = 0; j < lineL && expandLen < MAX_COLORIZE_LEN - tabW; j++) {
|
||||||
|
if (buf[lineOff + j] == '\t') {
|
||||||
|
int32_t spaces = tabW - (vc % tabW);
|
||||||
|
uint8_t sc = hasSyntax && j < MAX_COLORIZE_LEN ? rawSyntax[j] : 0;
|
||||||
|
|
||||||
|
for (int32_t s = 0; s < spaces && expandLen < MAX_COLORIZE_LEN; s++) {
|
||||||
|
expandBuf[expandLen] = ' ';
|
||||||
|
syntaxBuf[expandLen] = sc;
|
||||||
|
expandLen++;
|
||||||
|
vc++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expandBuf[expandLen] = buf[lineOff + j];
|
||||||
|
syntaxBuf[expandLen] = hasSyntax && j < MAX_COLORIZE_LEN ? rawSyntax[j] : 0;
|
||||||
|
expandLen++;
|
||||||
|
vc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible range within expanded line (visual columns)
|
||||||
|
int32_t scrollCol = ta->scrollCol;
|
||||||
|
int32_t visStart = scrollCol;
|
||||||
|
int32_t visEnd = scrollCol + visCols;
|
||||||
|
int32_t textEnd = expandLen;
|
||||||
|
|
||||||
|
// Clamp visible range to actual expanded content
|
||||||
|
int32_t drawStart = visStart < textEnd ? visStart : textEnd;
|
||||||
|
int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd;
|
||||||
|
|
||||||
|
// Determine selection intersection with this line.
|
||||||
|
// Selection offsets are byte-based; convert to visual columns in the expanded buffer.
|
||||||
int32_t lineSelLo = -1;
|
int32_t lineSelLo = -1;
|
||||||
int32_t lineSelHi = -1;
|
int32_t lineSelHi = -1;
|
||||||
|
|
||||||
if (selLo >= 0) {
|
if (selLo >= 0) {
|
||||||
if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
|
if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
|
||||||
lineSelLo = selLo - lineOff;
|
int32_t byteSelLo = selLo - lineOff;
|
||||||
lineSelHi = selHi - lineOff;
|
int32_t byteSelHi = selHi - lineOff;
|
||||||
if (lineSelLo < 0) { lineSelLo = 0; }
|
|
||||||
|
if (byteSelLo < 0) { byteSelLo = 0; }
|
||||||
|
|
||||||
|
lineSelLo = visualCol(buf, lineOff, lineOff + byteSelLo, tabW);
|
||||||
|
lineSelHi = visualCol(buf, lineOff, lineOff + (byteSelHi < lineL ? byteSelHi : lineL), tabW);
|
||||||
|
|
||||||
|
if (byteSelHi > lineL) {
|
||||||
|
lineSelHi = expandLen + 1; // extends past EOL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1900,23 +2110,23 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
// Before selection
|
// Before selection
|
||||||
if (drawStart < vSelLo) {
|
if (drawStart < vSelLo) {
|
||||||
if (hasSyntax) {
|
if (hasSyntax) {
|
||||||
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, syntaxBuf, drawStart, fg, bg, colors);
|
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, expandBuf + drawStart, vSelLo - drawStart, syntaxBuf, drawStart, fg, bg, colors);
|
||||||
} else {
|
} else {
|
||||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, fg, bg, true);
|
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, expandBuf + drawStart, vSelLo - drawStart, fg, bg, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection (always uses highlight colors, no syntax coloring)
|
// Selection (always uses highlight colors, no syntax coloring)
|
||||||
if (vSelLo < vSelHi) {
|
if (vSelLo < vSelHi) {
|
||||||
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, expandBuf + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// After selection
|
// After selection
|
||||||
if (vSelHi < drawEnd) {
|
if (vSelHi < drawEnd) {
|
||||||
if (hasSyntax) {
|
if (hasSyntax) {
|
||||||
drawColorizedText(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, syntaxBuf, vSelHi, fg, bg, colors);
|
drawColorizedText(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, expandBuf + vSelHi, drawEnd - vSelHi, syntaxBuf, vSelHi, fg, bg, colors);
|
||||||
} else {
|
} else {
|
||||||
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, fg, bg, true);
|
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, expandBuf + vSelHi, drawEnd - vSelHi, fg, bg, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1938,9 +2148,9 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
// No selection on this line
|
// No selection on this line
|
||||||
if (drawStart < drawEnd) {
|
if (drawStart < drawEnd) {
|
||||||
if (hasSyntax) {
|
if (hasSyntax) {
|
||||||
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, syntaxBuf, drawStart, fg, bg, colors);
|
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, expandBuf + drawStart, drawEnd - drawStart, syntaxBuf, drawStart, fg, bg, colors);
|
||||||
} else {
|
} else {
|
||||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, expandBuf + drawStart, drawEnd - drawStart, fg, bg, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1957,8 +2167,14 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
|
|
||||||
// Draw cursor (blinks at same rate as terminal cursor)
|
// Draw cursor (blinks at same rate as terminal cursor)
|
||||||
if (w == sFocusedWidget && sCursorBlinkOn) {
|
if (w == sFocusedWidget && sCursorBlinkOn) {
|
||||||
int32_t curDrawCol = ta->cursorCol - ta->scrollCol;
|
int32_t curLineOff = textAreaLineStartCached(w, ta->cursorRow);
|
||||||
int32_t curDrawRow = ta->cursorRow - ta->scrollRow;
|
int32_t curOff = curLineOff + ta->cursorCol;
|
||||||
|
|
||||||
|
if (curOff > len) { curOff = len; }
|
||||||
|
|
||||||
|
int32_t curVisCol = visualCol(buf, curLineOff, curOff, ta->tabWidth);
|
||||||
|
int32_t curDrawCol = curVisCol - ta->scrollCol;
|
||||||
|
int32_t curDrawRow = ta->cursorRow - ta->scrollRow;
|
||||||
|
|
||||||
if (curDrawCol >= 0 && curDrawCol <= visCols && curDrawRow >= 0 && curDrawRow < visRows) {
|
if (curDrawCol >= 0 && curDrawCol <= visCols && curDrawRow >= 0 && curDrawRow < visRows) {
|
||||||
int32_t cursorX = textX + curDrawCol * font->charWidth;
|
int32_t cursorX = textX + curDrawCol * font->charWidth;
|
||||||
|
|
@ -2431,8 +2647,8 @@ static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int3
|
||||||
ta->scrollRow++;
|
ta->scrollRow++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t clickRow = ta->scrollRow + relY / font->charHeight;
|
int32_t clickRow = ta->scrollRow + relY / font->charHeight;
|
||||||
int32_t clickCol = ta->scrollCol + relX / font->charWidth;
|
int32_t clickVisCol = ta->scrollCol + relX / font->charWidth;
|
||||||
|
|
||||||
if (clickRow < 0) {
|
if (clickRow < 0) {
|
||||||
clickRow = 0;
|
clickRow = 0;
|
||||||
|
|
@ -2442,11 +2658,14 @@ static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int3
|
||||||
clickRow = totalLines - 1;
|
clickRow = totalLines - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clickCol < 0) {
|
if (clickVisCol < 0) {
|
||||||
clickCol = 0;
|
clickVisCol = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t lineL = textAreaLineLen(ta->buf, ta->len, clickRow);
|
int32_t dragLineStart = textAreaLineStartCached(w, clickRow);
|
||||||
|
int32_t dragByteOff = visualColToOff(ta->buf, ta->len, dragLineStart, clickVisCol, ta->tabWidth);
|
||||||
|
int32_t clickCol = dragByteOff - dragLineStart;
|
||||||
|
int32_t lineL = textAreaLineLenCached(w, clickRow);
|
||||||
|
|
||||||
if (clickCol > lineL) {
|
if (clickCol > lineL) {
|
||||||
clickCol = lineL;
|
clickCol = lineL;
|
||||||
|
|
@ -2631,8 +2850,8 @@ void wgtTextAreaGoToLine(WidgetT *w, int32_t line) {
|
||||||
ta->desiredCol = 0;
|
ta->desiredCol = 0;
|
||||||
|
|
||||||
// Select the entire line for visual highlight
|
// Select the entire line for visual highlight
|
||||||
int32_t lineStart = textAreaLineStart(ta->buf, ta->len, row);
|
int32_t lineStart = textAreaLineStartCached(w, row);
|
||||||
int32_t lineL = textAreaLineLen(ta->buf, ta->len, row);
|
int32_t lineL = textAreaLineLenCached(w, row);
|
||||||
ta->selAnchor = lineStart;
|
ta->selAnchor = lineStart;
|
||||||
ta->selCursor = lineStart + lineL;
|
ta->selCursor = lineStart + lineL;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue