nolo-cli 0.33.0-alpha.20 → 0.33.0-alpha.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,7 @@ import {
24
24
  applyDetectedBackground,
25
25
  applyTerminalOutputToText,
26
26
  buildSkillContextBlocks,
27
+ buildSourceMapping,
27
28
  buildWindowTitle,
28
29
  clearCollapsedPasteStore,
29
30
  countPhysicalLines,
@@ -65,12 +66,14 @@ import {
65
66
  takeDisplayWidth,
66
67
  themeColorSequence,
67
68
  themeText,
69
+ tokenizeAnsiLine,
68
70
  truncateAnsi,
69
71
  userSurfaceBackgroundSequence,
70
72
  visibleWidth,
71
73
  wrapTextToLines,
72
- wrapTranscriptLine
73
- } from "./chunk-FR2AIV4Q.js";
74
+ wrapTranscriptLine,
75
+ wrapTranscriptLineWithLayout
76
+ } from "./chunk-AEYCNMJI.js";
74
77
  import "./chunk-ZHYK3N56.js";
75
78
  import {
76
79
  getReadableCliDb
@@ -1754,197 +1757,179 @@ function createSelectionState() {
1754
1757
  };
1755
1758
  }
1756
1759
  function compareSelectionPoints(a, b) {
1757
- if (a.turnIndex !== b.turnIndex) {
1758
- return a.turnIndex - b.turnIndex;
1760
+ if (a.globalRow !== b.globalRow) {
1761
+ return a.globalRow - b.globalRow;
1759
1762
  }
1760
- return a.sourceOffset - b.sourceOffset;
1763
+ return a.col - b.col;
1761
1764
  }
1762
1765
  function areSelectionPointsEqual(a, b) {
1763
1766
  if (a === null && b === null) return true;
1764
1767
  if (a === null || b === null) return false;
1765
- return a.turnIndex === b.turnIndex && a.sourceOffset === b.sourceOffset;
1766
- }
1767
- function hitTestHistory(history, screenRow, screenCol, contentWidth, scrollTop) {
1768
- if (history.turns.length === 0) return null;
1769
- const { entries } = buildTurnOffsets2(history, contentWidth);
1770
- if (entries.length === 0) return null;
1771
- const globalRow = scrollTop + screenRow;
1772
- const firstEntry = entries[0];
1773
- if (globalRow < firstEntry.startRow) {
1774
- return { turnIndex: 0, sourceOffset: 0 };
1775
- }
1776
- const lastIdx = entries.length - 1;
1777
- const lastEntry = entries[lastIdx];
1778
- const lastTurn = history.turns[lastIdx];
1779
- if (globalRow >= lastEntry.startRow + lastEntry.lineCount) {
1780
- return { turnIndex: lastIdx, sourceOffset: lastTurn.content.length };
1781
- }
1782
- for (let i = 0; i < entries.length; i++) {
1783
- const entry = entries[i];
1784
- const turn = history.turns[i];
1785
- const turnStart = entry.startRow;
1786
- const turnEnd = turnStart + entry.lineCount;
1787
- if (entry.separatorAbove > 0 && globalRow === turnStart - 1) {
1788
- return { turnIndex: i, sourceOffset: 0 };
1789
- }
1790
- if (globalRow >= turnStart && globalRow < turnEnd) {
1791
- const rowInTurn = globalRow - turnStart;
1792
- const sourceOffset = mapRowColToSourceOffset(
1793
- turn.content,
1794
- turn.role,
1795
- contentWidth,
1796
- rowInTurn,
1797
- screenCol,
1798
- turn.command
1799
- );
1800
- return { turnIndex: i, sourceOffset };
1768
+ return a.globalRow === b.globalRow && a.col === b.col;
1769
+ }
1770
+ function highlightLineByColumns(line, selStartCol, selEndCol, prefixWidth = 0) {
1771
+ const tokens = tokenizeAnsiLine(line);
1772
+ const range = resolveSelectableColumnRange(
1773
+ tokens,
1774
+ selStartCol,
1775
+ selEndCol,
1776
+ prefixWidth
1777
+ );
1778
+ if (!range) return line;
1779
+ let out = "";
1780
+ let currentCol = 0;
1781
+ let isHighlightActive = false;
1782
+ for (let i = 0; i < tokens.length; i++) {
1783
+ const token = tokens[i];
1784
+ if (token.kind === "sgr") {
1785
+ if (isHighlightActive) {
1786
+ if (/^\x1b\[0?m$/.test(token.value)) {
1787
+ out += `${token.value}\x1B[7m`;
1788
+ } else {
1789
+ out += token.value;
1790
+ }
1791
+ } else {
1792
+ out += token.value;
1793
+ }
1794
+ continue;
1801
1795
  }
1796
+ const charStartCol = currentCol;
1797
+ const charEndCol = currentCol + token.width;
1798
+ const isSelected = charStartCol >= range.start && charStartCol < range.end;
1799
+ if (isSelected && !isHighlightActive) {
1800
+ out += "\x1B[7m";
1801
+ isHighlightActive = true;
1802
+ } else if (!isSelected && isHighlightActive) {
1803
+ out += "\x1B[27m";
1804
+ isHighlightActive = false;
1805
+ }
1806
+ out += token.value;
1807
+ currentCol = charEndCol;
1802
1808
  }
1803
- return null;
1809
+ if (isHighlightActive) {
1810
+ out += "\x1B[27m";
1811
+ }
1812
+ return out;
1804
1813
  }
1805
- function mapRowColToSourceOffset(content, role, contentWidth, targetRow, targetCol, command) {
1806
- const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1807
- const logicalLines = normalized.split("\n");
1808
- let currentRow = 0;
1809
- let charAccumulator = 0;
1810
- if (role === "local" && command) {
1811
- const cmdWrapped = wrapTranscriptLine(`\u203A ${command}`, contentWidth);
1812
- if (targetRow < cmdWrapped.length) {
1813
- return 0;
1814
- }
1815
- currentRow += cmdWrapped.length;
1816
- }
1817
- for (let l = 0; l < logicalLines.length; l++) {
1818
- const lineText = logicalLines[l];
1819
- const lineLen = lineText.length;
1820
- let wrappedRows;
1821
- let prefixFirst = 0;
1822
- let prefixCont = 0;
1823
- if (role === "user") {
1824
- wrappedRows = wrapTranscriptLine(`\u2503 ${lineText}`, contentWidth, "\u2503 ");
1825
- prefixFirst = 3;
1826
- prefixCont = 3;
1827
- } else if (role === "local") {
1828
- wrappedRows = wrapTranscriptLine(` ${lineText}`, contentWidth, " ");
1829
- prefixFirst = 2;
1830
- prefixCont = 2;
1831
- } else {
1832
- if (l === 0 && !lineText.startsWith("[nolo]")) {
1833
- wrappedRows = wrapTranscriptLine(`\u25C8 ${lineText}`, contentWidth);
1834
- prefixFirst = 2;
1835
- prefixCont = 0;
1836
- } else {
1837
- wrappedRows = wrapTranscriptLine(lineText, contentWidth);
1838
- prefixFirst = 0;
1839
- prefixCont = 0;
1840
- }
1841
- }
1842
- const rowCount = Math.max(1, wrappedRows.length);
1843
- if (targetRow >= currentRow && targetRow < currentRow + rowCount) {
1844
- const subRow = targetRow - currentRow;
1845
- let offsetInLine = 0;
1846
- for (let s = 0; s < subRow; s++) {
1847
- const rowStr = stripAnsi(wrappedRows[s] ?? "");
1848
- const pLen2 = s === 0 ? prefixFirst : prefixCont;
1849
- const pureText = rowStr.slice(pLen2);
1850
- offsetInLine += pureText.length;
1851
- }
1852
- const targetRowStr = stripAnsi(wrappedRows[subRow] ?? "");
1853
- const pLen = subRow === 0 ? prefixFirst : prefixCont;
1854
- const pureTargetText = targetRowStr.slice(pLen);
1855
- const effectiveCol = Math.max(0, targetCol - pLen);
1856
- let colAccum = 0;
1857
- for (let c = 0; c < pureTargetText.length; c++) {
1858
- const char = pureTargetText[c];
1859
- const w = displayWidth(char);
1860
- if (colAccum + w > effectiveCol) {
1861
- break;
1862
- }
1863
- colAccum += w;
1864
- offsetInLine += char.length;
1865
- }
1866
- let finalOffset = Math.min(normalized.length, charAccumulator + Math.min(lineLen, offsetInLine));
1867
- if (finalOffset > 0 && finalOffset < normalized.length && normalized.charCodeAt(finalOffset - 1) >= 55296 && normalized.charCodeAt(finalOffset - 1) <= 56319) {
1868
- finalOffset += 1;
1869
- }
1870
- return finalOffset;
1814
+ function resolveSelectableColumnRange(tokens, requestedStart, requestedEnd, prefixWidth) {
1815
+ if (requestedStart >= requestedEnd) return null;
1816
+ let col = 0;
1817
+ let lastNonSpaceCol = 0;
1818
+ for (const token of tokens) {
1819
+ if (token.kind !== "char") continue;
1820
+ if (token.value.trim().length > 0) {
1821
+ lastNonSpaceCol = col + token.width;
1822
+ }
1823
+ col += token.width;
1824
+ }
1825
+ const start = Math.max(prefixWidth, requestedStart);
1826
+ const end = Math.min(requestedEnd, lastNonSpaceCol);
1827
+ if (start >= end) return null;
1828
+ let snappedStart = null;
1829
+ let snappedEnd = null;
1830
+ col = 0;
1831
+ for (const token of tokens) {
1832
+ if (token.kind !== "char") continue;
1833
+ const tokenStart = col;
1834
+ const tokenEnd = col + token.width;
1835
+ if (tokenEnd > start && tokenStart < end) {
1836
+ snappedStart ??= tokenStart;
1837
+ snappedEnd = tokenEnd;
1838
+ }
1839
+ col = tokenEnd;
1840
+ }
1841
+ return snappedStart === null || snappedEnd === null ? null : { start: snappedStart, end: snappedEnd };
1842
+ }
1843
+ function hitTestHistory(history, screenRow, screenCol, _contentWidth, scrollTop) {
1844
+ const globalRow = Math.max(0, scrollTop + screenRow);
1845
+ const col = Math.max(0, screenCol);
1846
+ return { globalRow, col };
1847
+ }
1848
+ function extractTextSliceByColumns(line, startCol, endCol, prefixWidth = 0) {
1849
+ const tokens = tokenizeAnsiLine(line);
1850
+ const range = resolveSelectableColumnRange(
1851
+ tokens,
1852
+ startCol,
1853
+ endCol,
1854
+ prefixWidth
1855
+ );
1856
+ if (!range) return "";
1857
+ let col = 0;
1858
+ let text = "";
1859
+ for (const token of tokens) {
1860
+ if (token.kind !== "char") continue;
1861
+ if (col >= range.start && col < range.end) {
1862
+ text += token.value;
1871
1863
  }
1872
- currentRow += rowCount;
1873
- charAccumulator += lineLen + 1;
1864
+ col += token.width;
1874
1865
  }
1875
- return normalized.length;
1866
+ return text;
1876
1867
  }
1877
- function extractSelectedText(history, anchor, head) {
1868
+ function extractSelectedText(history, anchor, head, contentWidth) {
1878
1869
  if (!anchor || !head) return "";
1879
1870
  const cmp = compareSelectionPoints(anchor, head);
1880
1871
  if (cmp === 0) return "";
1881
1872
  const start = cmp < 0 ? anchor : head;
1882
1873
  const end = cmp < 0 ? head : anchor;
1883
- if (start.turnIndex === end.turnIndex) {
1884
- const turn = history.turns[start.turnIndex];
1885
- if (!turn) return "";
1886
- const raw = turn.content.slice(start.sourceOffset, end.sourceOffset);
1887
- return stripAnsi(raw);
1888
- }
1874
+ const rows = buildHistoryLayoutRows(history, contentWidth);
1889
1875
  const pieces = [];
1890
- const startTurn2 = history.turns[start.turnIndex];
1891
- if (startTurn2) {
1892
- pieces.push(startTurn2.content.slice(start.sourceOffset));
1893
- }
1894
- for (let i = start.turnIndex + 1; i < end.turnIndex; i++) {
1895
- const midTurn = history.turns[i];
1896
- if (midTurn) {
1897
- pieces.push(midTurn.content);
1876
+ for (let r = start.globalRow; r <= end.globalRow; r++) {
1877
+ const row = rows[r];
1878
+ if (!row) continue;
1879
+ let lineStartCol = 0;
1880
+ let lineEndCol = Infinity;
1881
+ if (r === start.globalRow) {
1882
+ lineStartCol = start.col;
1883
+ }
1884
+ if (r === end.globalRow) {
1885
+ lineEndCol = end.col;
1886
+ }
1887
+ pieces.push(
1888
+ extractTextSliceByColumns(
1889
+ row.rendered,
1890
+ lineStartCol,
1891
+ lineEndCol,
1892
+ row.prefixWidth
1893
+ )
1894
+ );
1895
+ if (r < end.globalRow) {
1896
+ pieces.push(row.softWrapped ? row.softWrapJoiner ?? "" : "\n");
1898
1897
  }
1899
1898
  }
1900
- const endTurn = history.turns[end.turnIndex];
1901
- if (endTurn) {
1902
- pieces.push(endTurn.content.slice(0, end.sourceOffset));
1903
- }
1904
- return stripAnsi(pieces.join("\n\n"));
1899
+ return pieces.join("");
1905
1900
  }
1906
1901
  function applySelectionOverlay(visibleLines, history, contentWidth, scrollTop, selection) {
1907
- if (!selection.dragging || !selection.anchor || !selection.head) {
1902
+ if (!selection.anchor || !selection.head) {
1908
1903
  return visibleLines;
1909
1904
  }
1910
1905
  const cmp = compareSelectionPoints(selection.anchor, selection.head);
1911
1906
  if (cmp === 0) return visibleLines;
1912
1907
  const start = cmp < 0 ? selection.anchor : selection.head;
1913
1908
  const end = cmp < 0 ? selection.head : selection.anchor;
1914
- const { entries } = buildTurnOffsets2(history, contentWidth);
1909
+ const layoutRows = buildHistoryLayoutRows(history, contentWidth);
1915
1910
  const result = [...visibleLines];
1916
1911
  for (let screenRow = 0; screenRow < visibleLines.length; screenRow++) {
1917
1912
  const globalRow = scrollTop + screenRow;
1918
- let isSelectedRow = false;
1919
- for (let i = start.turnIndex; i <= end.turnIndex; i++) {
1920
- const entry = entries[i];
1921
- if (!entry) continue;
1922
- const turnStart = entry.startRow;
1923
- const turnEnd = turnStart + entry.lineCount;
1924
- if (i === start.turnIndex && i === end.turnIndex) {
1925
- if (globalRow >= turnStart && globalRow < turnEnd) {
1926
- isSelectedRow = true;
1927
- }
1928
- } else if (i === start.turnIndex) {
1929
- if (globalRow >= turnStart) {
1930
- isSelectedRow = true;
1931
- }
1932
- } else if (i === end.turnIndex) {
1933
- if (globalRow < turnEnd) {
1934
- isSelectedRow = true;
1935
- }
1936
- } else {
1937
- if (globalRow >= turnStart && globalRow < turnEnd) {
1938
- isSelectedRow = true;
1939
- }
1940
- }
1941
- }
1942
- if (isSelectedRow) {
1943
- const line = result[screenRow] ?? "";
1944
- if (line.length > 0) {
1945
- result[screenRow] = `\x1B[7m${line}\x1B[27m`;
1946
- }
1913
+ if (globalRow < start.globalRow || globalRow > end.globalRow) {
1914
+ continue;
1947
1915
  }
1916
+ const currentLine = result[screenRow] ?? "";
1917
+ if (currentLine.length === 0) continue;
1918
+ const layoutRow = layoutRows[globalRow];
1919
+ let selStartCol = 0;
1920
+ let selEndCol = Infinity;
1921
+ if (globalRow === start.globalRow) {
1922
+ selStartCol = start.col;
1923
+ }
1924
+ if (globalRow === end.globalRow) {
1925
+ selEndCol = end.col;
1926
+ }
1927
+ result[screenRow] = highlightLineByColumns(
1928
+ currentLine,
1929
+ selStartCol,
1930
+ selEndCol,
1931
+ layoutRow?.prefixWidth ?? 0
1932
+ );
1948
1933
  }
1949
1934
  return result;
1950
1935
  }
@@ -1959,7 +1944,6 @@ function resetHistoryFrameDiffCache(output) {
1959
1944
  frameBufferByOutput.delete(output);
1960
1945
  }
1961
1946
  }
1962
- var renderCacheMissCount = 0;
1963
1947
  function createTurnHistory() {
1964
1948
  return {
1965
1949
  turns: [],
@@ -1970,11 +1954,8 @@ function createTurnHistory() {
1970
1954
  };
1971
1955
  }
1972
1956
  function startTurn(history, role) {
1973
- if (history.currentRole !== null) {
1974
- history.turns.push({
1975
- role: history.currentRole,
1976
- content: history.currentContent
1977
- });
1957
+ if (history.currentRole !== null && history.currentContent) {
1958
+ finalizeCurrentTurn(history);
1978
1959
  }
1979
1960
  history.currentRole = role;
1980
1961
  history.currentContent = "";
@@ -2036,9 +2017,8 @@ function fillUserBubbleRow(row, surfaceSeq, contentWidth) {
2036
2017
  }
2037
2018
  return renderSurfaceLine({ text: row, surface: surfaceSeq, padTo: contentWidth });
2038
2019
  }
2039
- function renderTurnBlock(role, content, contentWidth, colorEnabled, command) {
2020
+ function layoutTurnRows(role, content, contentWidth, colorEnabled, command) {
2040
2021
  content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2041
- const lines = [];
2042
2022
  if (role === "user") {
2043
2023
  const logicalLines = content.split("\n");
2044
2024
  const accentSeq = colorEnabled ? themeColorSequence("accent") : "";
@@ -2046,37 +2026,144 @@ function renderTurnBlock(role, content, contentWidth, colorEnabled, command) {
2046
2026
  const multilinePrefix = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2047
2027
  const hangingIndent = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2048
2028
  const surfaceSeq = colorEnabled ? userSurfaceBackgroundSequence() : "";
2029
+ const prefixWidth = 3;
2030
+ let lineSourceStart = 0;
2031
+ const rows2 = [];
2049
2032
  for (let i = 0; i < logicalLines.length; i++) {
2050
2033
  const line = logicalLines[i];
2051
2034
  const prefix = i === 0 ? firstPrefix : multilinePrefix;
2052
2035
  const styledLine = `${prefix}${line}`;
2053
- const rows = wrapTranscriptLine(styledLine, contentWidth, hangingIndent);
2054
- lines.push(
2055
- ...surfaceSeq ? rows.map((row) => fillUserBubbleRow(row, surfaceSeq, contentWidth)) : rows
2036
+ const prefixCharCount = prefix.length;
2037
+ const wrappedRows = wrapTranscriptLineWithLayout(
2038
+ styledLine,
2039
+ contentWidth,
2040
+ hangingIndent,
2041
+ lineSourceStart,
2042
+ prefixWidth,
2043
+ prefixCharCount
2056
2044
  );
2045
+ for (const r of wrappedRows) {
2046
+ const rendered = surfaceSeq ? fillUserBubbleRow(r.rendered, surfaceSeq, contentWidth) : r.rendered;
2047
+ rows2.push({
2048
+ rendered,
2049
+ sourceStart: r.sourceStart,
2050
+ sourceEnd: r.sourceEnd,
2051
+ prefixWidth: r.prefixWidth,
2052
+ sourceMapping: r.sourceMapping,
2053
+ softWrapped: r.softWrapped,
2054
+ softWrapJoiner: r.softWrapJoiner
2055
+ });
2056
+ }
2057
+ lineSourceStart += line.length + 1;
2057
2058
  }
2058
- } else if (role === "local") {
2059
+ return rows2.length > 0 ? rows2 : [{ rendered: surfaceSeq ? fillUserBubbleRow(firstPrefix, surfaceSeq, contentWidth) : firstPrefix, sourceStart: 0, sourceEnd: 0, prefixWidth: 3 }];
2060
+ }
2061
+ if (role === "local") {
2059
2062
  const dimSeq = colorEnabled ? "\x1B[2m" : "";
2060
2063
  const resetSeq = colorEnabled ? "\x1B[0m" : "";
2064
+ const rows2 = [];
2061
2065
  if (command) {
2062
- lines.push(
2063
- ...wrapTranscriptLine(`${dimSeq}\u203A ${command}${resetSeq}`, contentWidth)
2066
+ const cmdPrefix = `${dimSeq}\u203A `;
2067
+ const cmdLine = `${cmdPrefix}${command}${resetSeq}`;
2068
+ const cmdRows = wrapTranscriptLineWithLayout(
2069
+ cmdLine,
2070
+ contentWidth,
2071
+ "",
2072
+ 0,
2073
+ 2,
2074
+ cmdPrefix.length
2064
2075
  );
2076
+ for (const r of cmdRows) {
2077
+ rows2.push({
2078
+ rendered: r.rendered,
2079
+ sourceStart: 0,
2080
+ sourceEnd: 0,
2081
+ prefixWidth: 2,
2082
+ sourceMapping: void 0,
2083
+ softWrapped: r.softWrapped,
2084
+ softWrapJoiner: r.softWrapJoiner
2085
+ });
2086
+ }
2065
2087
  }
2088
+ let lineSourceStart = 0;
2066
2089
  if (content) {
2067
2090
  for (const line of content.split("\n")) {
2068
- lines.push(
2069
- ...wrapTranscriptLine(`${dimSeq} ${line}${resetSeq}`, contentWidth)
2091
+ const prefix = `${dimSeq} `;
2092
+ const styledLine = `${prefix}${line}${resetSeq}`;
2093
+ const prefixCharCount = prefix.length;
2094
+ const wrappedRows = wrapTranscriptLineWithLayout(
2095
+ styledLine,
2096
+ contentWidth,
2097
+ " ",
2098
+ lineSourceStart,
2099
+ 2,
2100
+ prefixCharCount
2070
2101
  );
2071
- }
2072
- }
2073
- } else {
2074
- const styledEntry = styleAssistantTurn(content, colorEnabled);
2075
- for (const logicalLine of styledEntry.split("\n")) {
2076
- lines.push(...wrapTranscriptLine(logicalLine, contentWidth));
2102
+ rows2.push(...wrappedRows);
2103
+ lineSourceStart += line.length + 1;
2104
+ }
2105
+ }
2106
+ return rows2.length > 0 ? rows2 : [{ rendered: "", sourceStart: 0, sourceEnd: 0, prefixWidth: 2 }];
2107
+ }
2108
+ const styledEntry = styleAssistantTurn(content, colorEnabled);
2109
+ const styledLines = styledEntry.split("\n");
2110
+ const rawLines = content.split("\n");
2111
+ let rawIdx = 0;
2112
+ let rawOffset = 0;
2113
+ const rows = [];
2114
+ for (let i = 0; i < styledLines.length; i++) {
2115
+ const styledLine = styledLines[i];
2116
+ const isFirstLine = i === 0 && !styledLine.startsWith("[nolo]");
2117
+ const anchorPrefix = isFirstLine ? colorEnabled ? `${themeColorSequence("chrome")}\u25C8\x1B[39m ` : "\u25C8 " : "";
2118
+ const prefixWidth = isFirstLine ? 2 : 0;
2119
+ const prefixCharCount = anchorPrefix.length;
2120
+ if (styledLine === "" && rawIdx < rawLines.length && rawLines[rawIdx] !== "") {
2121
+ rows.push({
2122
+ rendered: "",
2123
+ sourceStart: rawOffset,
2124
+ sourceEnd: rawOffset,
2125
+ prefixWidth: 0
2126
+ });
2127
+ continue;
2077
2128
  }
2078
- }
2079
- return lines;
2129
+ const rawLine = rawIdx < rawLines.length ? rawLines[rawIdx] : "";
2130
+ const rawLineLen = rawLine.length;
2131
+ const sourceMapping = buildSourceMapping(rawLine, styledLine, prefixCharCount);
2132
+ const lineRows = wrapTranscriptLineWithLayout(
2133
+ styledLine,
2134
+ contentWidth,
2135
+ "",
2136
+ rawOffset,
2137
+ prefixWidth,
2138
+ prefixCharCount,
2139
+ sourceMapping,
2140
+ rawLineLen
2141
+ );
2142
+ rows.push(...lineRows);
2143
+ rawIdx += 1;
2144
+ rawOffset += rawLineLen + 1;
2145
+ }
2146
+ return rows.length > 0 ? rows : [{ rendered: "", sourceStart: 0, sourceEnd: 0, prefixWidth: 0 }];
2147
+ }
2148
+ function getTurnLayoutRows(turn, contentWidth, colorEnabled, density, surface) {
2149
+ const cached = turnLineCache.get(turn);
2150
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.surface === surface) {
2151
+ return cached.layoutRows;
2152
+ }
2153
+ const layoutRows = layoutTurnRows(turn.role, turn.content, contentWidth, colorEnabled, turn.command);
2154
+ const lines = layoutRows.map((r) => r.rendered);
2155
+ turnLineCache.set(turn, {
2156
+ width: contentWidth,
2157
+ color: colorEnabled,
2158
+ density,
2159
+ surface,
2160
+ lines,
2161
+ layoutRows
2162
+ });
2163
+ return layoutRows;
2164
+ }
2165
+ function renderTurnBlock(role, content, contentWidth, colorEnabled, command) {
2166
+ return layoutTurnRows(role, content, contentWidth, colorEnabled, command).map((r) => r.rendered);
2080
2167
  }
2081
2168
  var streamingTurnCache = null;
2082
2169
  function resetStreamingTurnCache() {
@@ -2104,12 +2191,12 @@ function renderPrefixTurnBlock(role, content, contentWidth, colorEnabled) {
2104
2191
  }
2105
2192
  function renderTailTurnBlock(role, content, contentWidth, colorEnabled) {
2106
2193
  content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2107
- const lines = [];
2108
2194
  if (role === "user") {
2109
2195
  const surfaceSeq = colorEnabled ? userSurfaceBackgroundSequence() : "";
2110
2196
  const accentSeq = colorEnabled ? themeColorSequence("accent") : "";
2111
2197
  const multilinePrefix = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2112
2198
  const hangingIndent = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2199
+ const lines = [];
2113
2200
  for (const rawLine of content.split("\n")) {
2114
2201
  const styledLine = `${multilinePrefix}${rawLine}`;
2115
2202
  const rows = wrapTranscriptLine(styledLine, contentWidth, hangingIndent);
@@ -2117,17 +2204,19 @@ function renderTailTurnBlock(role, content, contentWidth, colorEnabled) {
2117
2204
  ...surfaceSeq ? rows.map((row) => fillUserBubbleRow(row, surfaceSeq, contentWidth)) : rows
2118
2205
  );
2119
2206
  }
2207
+ return lines;
2120
2208
  } else {
2121
- const highlighted = colorEnabled ? formatAssistantDisplay(content) : stripAnsi(formatAssistantDisplay(content));
2209
+ const highlighted = colorEnabled ? formatAssistantDisplay(content, { trimEdges: false }) : stripAnsi(formatAssistantDisplay(content, { trimEdges: false }));
2122
2210
  const rawLines = highlighted.split("\n");
2123
2211
  const styledLines = rawLines.map((line) => {
2124
2212
  return line.startsWith("[nolo]") && colorEnabled ? themeText(line, "chrome", true) : line;
2125
2213
  });
2214
+ const lines = [];
2126
2215
  for (const logicalLine of styledLines) {
2127
2216
  lines.push(...wrapTranscriptLine(logicalLine, contentWidth));
2128
2217
  }
2218
+ return lines;
2129
2219
  }
2130
- return lines;
2131
2220
  }
2132
2221
  function getStreamingTurnLines(role, content, contentWidth, colorEnabled, density, surface) {
2133
2222
  if (streamingTurnCache && (streamingTurnCache.role !== role || streamingTurnCache.contentWidth !== contentWidth || streamingTurnCache.colorEnabled !== colorEnabled || streamingTurnCache.density !== density || streamingTurnCache.surface !== surface || !content.startsWith(streamingTurnCache.fullContent.slice(0, streamingTurnCache.prefixLength)))) {
@@ -2185,51 +2274,10 @@ function getStreamingTurnLines(role, content, contentWidth, colorEnabled, densit
2185
2274
  const tailLines = renderTailTurnBlock(role, tailContent, contentWidth, colorEnabled);
2186
2275
  return [...streamingTurnCache.prefixLines, ...tailLines];
2187
2276
  }
2188
- var assistantPlainLinesCache = /* @__PURE__ */ new Map();
2189
- function getAssistantPlainLines(content) {
2190
- let plainLines = assistantPlainLinesCache.get(content);
2191
- if (plainLines !== void 0) {
2192
- return plainLines;
2193
- }
2194
- if (!/[\r\x00#|\-*_~`>+\[\]()•☐☑]|\b\d+\./.test(content)) {
2195
- plainLines = content.trim().split("\n");
2196
- } else {
2197
- plainLines = stripAnsi(formatAssistantDisplay(content)).split("\n");
2198
- }
2199
- if (assistantPlainLinesCache.size > 2e3) {
2200
- const firstKey = assistantPlainLinesCache.keys().next().value;
2201
- if (firstKey !== void 0) assistantPlainLinesCache.delete(firstKey);
2202
- }
2203
- assistantPlainLinesCache.set(content, plainLines);
2204
- return plainLines;
2205
- }
2206
2277
  function countTurnLines(role, content, contentWidth, command) {
2207
- content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2208
- let count = 0;
2209
- if (role === "user") {
2210
- for (const line of content.split("\n")) {
2211
- count += wrapTranscriptLine(`\u2503 ${line}`, contentWidth, "\u2503 ").length;
2212
- }
2213
- } else if (role === "local") {
2214
- if (command) {
2215
- count += wrapTranscriptLine(`\u203A ${command}`, contentWidth).length;
2216
- }
2217
- if (content) {
2218
- for (const line of content.split("\n")) {
2219
- count += wrapTranscriptLine(` ${line}`, contentWidth).length;
2220
- }
2221
- }
2222
- } else {
2223
- const plainLines = getAssistantPlainLines(content);
2224
- for (let i = 0; i < plainLines.length; i++) {
2225
- const line = plainLines[i];
2226
- const wrapped = i === 0 && !line.startsWith("[nolo]") ? wrapTranscriptLine(`\u25C8 ${line}`, contentWidth) : wrapTranscriptLine(line, contentWidth);
2227
- count += wrapped.length;
2228
- }
2229
- }
2230
- return count;
2278
+ return layoutTurnRows(role, content, contentWidth, false, command).length;
2231
2279
  }
2232
- function buildTurnOffsets2(history, contentWidth) {
2280
+ function buildTurnOffsets(history, contentWidth) {
2233
2281
  const density = getActiveDensity();
2234
2282
  const entries = [];
2235
2283
  let offset = 0;
@@ -2250,6 +2298,36 @@ function buildTurnOffsets2(history, contentWidth) {
2250
2298
  }
2251
2299
  return { entries, totalLines: offset };
2252
2300
  }
2301
+ function getAllTurnEntries(history, contentWidth) {
2302
+ const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2303
+ const turns = [...history.turns];
2304
+ if (history.currentRole !== null && history.currentContent) {
2305
+ const density = getActiveDensity();
2306
+ const colorEnabled = resolveCliColorEnabled();
2307
+ const surface = colorEnabled ? userSurfaceBackgroundSequence() : "";
2308
+ const streamingLines = getStreamingTurnLines(
2309
+ history.currentRole,
2310
+ history.currentContent,
2311
+ contentWidth,
2312
+ colorEnabled,
2313
+ density,
2314
+ surface
2315
+ );
2316
+ const sep = currentTurnSeparator(history, density);
2317
+ const startRow = finalizedLines + sep;
2318
+ entries.push({
2319
+ startRow,
2320
+ lineCount: streamingLines.length,
2321
+ separatorAbove: sep
2322
+ });
2323
+ turns.push({
2324
+ role: history.currentRole,
2325
+ content: history.currentContent
2326
+ });
2327
+ return { entries, totalLines: startRow + streamingLines.length, turns };
2328
+ }
2329
+ return { entries, totalLines: finalizedLines, turns };
2330
+ }
2253
2331
  function currentTurnSeparator(history, density) {
2254
2332
  return density === "spacious" && (history.turns.length > 0 || history.currentRole === "user") ? 1 : 0;
2255
2333
  }
@@ -2273,6 +2351,41 @@ function buildCopyViewLines(history) {
2273
2351
  }
2274
2352
  return lines;
2275
2353
  }
2354
+ function buildHistoryLayoutRows(history, contentWidth) {
2355
+ const colorEnabled = resolveCliColorEnabled();
2356
+ const density = getActiveDensity();
2357
+ const surface = colorEnabled ? userSurfaceBackgroundSequence() : "";
2358
+ const rows = [];
2359
+ const separatorRow = () => ({
2360
+ rendered: "",
2361
+ sourceStart: 0,
2362
+ sourceEnd: 0,
2363
+ prefixWidth: 0
2364
+ });
2365
+ for (let i = 0; i < history.turns.length; i++) {
2366
+ const turn = history.turns[i];
2367
+ if (density === "spacious" && (i > 0 || turn.role === "user")) {
2368
+ rows.push(separatorRow());
2369
+ }
2370
+ rows.push(...getTurnLayoutRows(turn, contentWidth, colorEnabled, density, surface));
2371
+ }
2372
+ if (history.currentRole !== null && history.currentContent) {
2373
+ const i = history.turns.length;
2374
+ if (density === "spacious" && (i > 0 || history.currentRole === "user")) {
2375
+ rows.push(separatorRow());
2376
+ }
2377
+ rows.push(
2378
+ ...layoutTurnRows(
2379
+ history.currentRole,
2380
+ history.currentContent,
2381
+ contentWidth,
2382
+ colorEnabled
2383
+ )
2384
+ );
2385
+ }
2386
+ return rows;
2387
+ }
2388
+ var renderCacheMissCount = 0;
2276
2389
  function renderHistory(output, history, inputLines, selection) {
2277
2390
  const tty = output;
2278
2391
  if (!tty.isTTY) return;
@@ -2283,13 +2396,9 @@ function renderHistory(output, history, inputLines, selection) {
2283
2396
  const colorEnabled = resolveCliColorEnabled();
2284
2397
  const density = getActiveDensity();
2285
2398
  const surface = colorEnabled ? userSurfaceBackgroundSequence() : "";
2286
- const { entries, totalLines: finalizedLines } = buildTurnOffsets2(
2287
- history,
2288
- contentWidth
2289
- );
2290
- let totalLines = finalizedLines;
2291
- let currentStart = -1;
2399
+ const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2292
2400
  let currentLines = [];
2401
+ let currentStart = -1;
2293
2402
  if (history.currentRole !== null && history.currentContent) {
2294
2403
  currentLines = getStreamingTurnLines(
2295
2404
  history.currentRole,
@@ -2299,16 +2408,17 @@ function renderHistory(output, history, inputLines, selection) {
2299
2408
  density,
2300
2409
  surface
2301
2410
  );
2302
- currentStart = finalizedLines + currentTurnSeparator(history, density);
2303
- totalLines = currentStart + currentLines.length;
2411
+ const sep = currentTurnSeparator(history, density);
2412
+ currentStart = finalizedLines + sep;
2304
2413
  }
2414
+ const totalLines = currentStart >= 0 ? currentStart + currentLines.length : finalizedLines;
2305
2415
  if (history.followBottom) {
2306
2416
  history.scrollTop = Math.max(0, totalLines - visibleHeight);
2307
2417
  } else {
2308
- history.scrollTop = Math.max(
2309
- 0,
2310
- Math.min(history.scrollTop, Math.max(0, totalLines - visibleHeight))
2311
- );
2418
+ const maxScroll = Math.max(0, totalLines - visibleHeight);
2419
+ if (history.scrollTop > maxScroll) {
2420
+ history.scrollTop = maxScroll;
2421
+ }
2312
2422
  }
2313
2423
  history.hasMoreAbove = history.scrollTop > 0;
2314
2424
  history.hasMoreBelow = history.scrollTop + visibleHeight < totalLines;
@@ -2328,20 +2438,14 @@ function renderHistory(output, history, inputLines, selection) {
2328
2438
  if (turnEnd <= winStart || turnStart >= winEnd) continue;
2329
2439
  const turn = history.turns[i];
2330
2440
  const cached = turnLineCache.get(turn);
2331
- let lines;
2441
+ let layoutRows;
2332
2442
  if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.surface === surface) {
2333
- lines = cached.lines;
2443
+ layoutRows = cached.layoutRows;
2334
2444
  } else {
2335
2445
  renderCacheMissCount += 1;
2336
- lines = renderTurnBlock(turn.role, turn.content, contentWidth, colorEnabled, turn.command);
2337
- turnLineCache.set(turn, {
2338
- width: contentWidth,
2339
- color: colorEnabled,
2340
- density,
2341
- surface,
2342
- lines
2343
- });
2446
+ layoutRows = getTurnLayoutRows(turn, contentWidth, colorEnabled, density, surface);
2344
2447
  }
2448
+ const lines = layoutRows.map((r) => r.rendered);
2345
2449
  const interStart = Math.max(turnStart, winStart);
2346
2450
  const interEnd = Math.min(turnEnd, winEnd);
2347
2451
  for (let r = interStart; r < interEnd; r++) {
@@ -2365,7 +2469,7 @@ function renderHistory(output, history, inputLines, selection) {
2365
2469
  }
2366
2470
  }
2367
2471
  }
2368
- if (selection && selection.dragging) {
2472
+ if (selection && selection.anchor && selection.head) {
2369
2473
  visibleLines = applySelectionOverlay(
2370
2474
  visibleLines,
2371
2475
  history,
@@ -2377,11 +2481,11 @@ function renderHistory(output, history, inputLines, selection) {
2377
2481
  const prevBuffer = typeof output === "object" && output !== null ? frameBufferByOutput.get(output) : void 0;
2378
2482
  const isGeometryCompatible = prevBuffer !== void 0 && prevBuffer.rows === rows && prevBuffer.columns === columns && prevBuffer.inputLines === inputLines && prevBuffer.lines.length === visibleHeight;
2379
2483
  const prevLines = isGeometryCompatible ? prevBuffer.lines : void 0;
2380
- const nextLines = new Array(visibleHeight);
2381
2484
  let frame = "";
2485
+ const nextLines = new Array(visibleHeight);
2382
2486
  for (let i = 0; i < visibleHeight; i++) {
2383
- const line = visibleLines[i] ?? "";
2384
- const padded = padOrTruncateToWidth(line, contentWidth);
2487
+ const content = visibleLines[i] ?? "";
2488
+ const padded = padOrTruncateToWidth(content, contentWidth);
2385
2489
  const thumb = renderScrollbarRow(i, visibleHeight, totalLines, history.scrollTop);
2386
2490
  const scrollbarPrefix = colorEnabled ? themeColorSequence("chrome") : "";
2387
2491
  const scrollbarSuffix = colorEnabled ? "\x1B[39m" : "";
@@ -2407,9 +2511,6 @@ function renderHistory(output, history, inputLines, selection) {
2407
2511
  }
2408
2512
  function createHistoryOutputStream(history, onUpdate) {
2409
2513
  return {
2410
- // Virtual TTY: Spinner uses \\r in-place updates. We honor that via
2411
- // applyTerminalOutputToText so frames collapse to one status line instead
2412
- // of spamming the transcript. Do not fall through to process.stdout here.
2413
2514
  isTTY: true,
2414
2515
  write(chunk) {
2415
2516
  const text = typeof chunk === "string" ? chunk : chunk.toString();
@@ -2426,7 +2527,7 @@ function applyScrollAction(history, action, output, inputLines) {
2426
2527
  const columns = tty.columns ?? 80;
2427
2528
  const visibleHeight = Math.max(1, rows - inputLines);
2428
2529
  const contentWidth = Math.max(1, columns - 1);
2429
- const { totalLines: finalizedLines } = buildTurnOffsets2(history, contentWidth);
2530
+ const { totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2430
2531
  let totalLines = finalizedLines;
2431
2532
  if (history.currentRole !== null && history.currentContent) {
2432
2533
  totalLines += currentTurnSeparator(history, getActiveDensity()) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
@@ -6317,6 +6418,10 @@ async function startTuiWorkspace(options) {
6317
6418
  const output = options.output ?? defaultOutput;
6318
6419
  const fetchImpl = options.fetchImpl ?? fetch;
6319
6420
  const spawnRunner = options.spawnRunner ?? spawnProcess;
6421
+ const writeClipboard = options.clipboardWriter ?? (async (text) => {
6422
+ const { default: clipboard } = await import("./clipboardy-MMBYVRLF.js");
6423
+ await clipboard.write(text);
6424
+ });
6320
6425
  const selfUpdater = options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
6321
6426
  if (output.isTTY) {
6322
6427
  enterAltScreen(output);
@@ -7053,8 +7158,7 @@ async function startTuiWorkspace(options) {
7053
7158
  emitCommandOutput(t("copyNothing"));
7054
7159
  } else {
7055
7160
  try {
7056
- const { default: clipboard } = await import("./clipboardy-MMBYVRLF.js");
7057
- await clipboard.write(text);
7161
+ await writeClipboard(text);
7058
7162
  emitCommandOutput(t("copiedLastReply"));
7059
7163
  } catch (error) {
7060
7164
  const message = toErrorMessage(error);
@@ -7406,7 +7510,7 @@ ${err.message}` : ""}`
7406
7510
  stopAutoScroll();
7407
7511
  }
7408
7512
  } else {
7409
- const { totalLines } = buildTurnOffsets(history, contentWidth);
7513
+ const { totalLines } = getAllTurnEntries(history, contentWidth);
7410
7514
  const maxScroll = Math.max(0, totalLines - visibleHeight);
7411
7515
  if (history.scrollTop < maxScroll) {
7412
7516
  history.scrollTop = Math.min(maxScroll, history.scrollTop + 1);
@@ -7567,19 +7671,28 @@ ${merged}` : merged;
7567
7671
  const textToCopy = extractSelectedText(
7568
7672
  history,
7569
7673
  selectionState.anchor,
7570
- selectionState.head
7674
+ selectionState.head,
7675
+ contentWidth
7571
7676
  );
7572
7677
  if (textToCopy.length > 0) {
7573
- import("./clipboardy-MMBYVRLF.js").then(({ default: clipboard }) => clipboard.write(textToCopy)).catch(() => {
7678
+ writeClipboard(textToCopy).catch((error) => {
7679
+ emitCommandOutput(
7680
+ `[nolo] ${t("copyFailed")}: ${toErrorMessage(error)}`
7681
+ );
7574
7682
  });
7575
7683
  }
7684
+ selectionState.dragging = false;
7685
+ } else {
7686
+ clearSelection();
7576
7687
  }
7577
- clearSelection();
7578
7688
  paintFrame(buffer);
7579
7689
  return;
7580
7690
  }
7581
7691
  return;
7582
7692
  }
7693
+ if (selectionState.anchor) {
7694
+ clearSelection();
7695
+ }
7583
7696
  const scrollAction = parseScrollAction(sequence);
7584
7697
  if (scrollAction) {
7585
7698
  if (fixedInput.isPaused()) return;