bunmicro 0.9.5 → 0.9.10
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.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/index.js +64 -13
- package/todo.txt +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.10] - 2026-06-04
|
|
4
|
+
- Up/Down key auto-complete selection in editor
|
|
5
|
+
|
|
6
|
+
## [0.9.9] - 2026-06-04
|
|
7
|
+
- Added long line protection for softwrap
|
|
8
|
+
* That means binary edits available
|
|
9
|
+
* you can now open libc.so.6
|
|
10
|
+
* Ctrl+E reopen hex3 to edit & save
|
|
11
|
+
- Fixed softwrap search match cross line
|
|
12
|
+
|
|
3
13
|
## [0.9.5] - 2026-06-03
|
|
4
14
|
- Added encoding hex3 for binary edit
|
|
5
15
|
- term better supports fish
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
- Almost every component on the screen is clickable or double clickable
|
|
41
41
|
- A complete help is at the end
|
|
42
42
|
## Auto-completions arrow keys
|
|
43
|
-
- Press Tab and use
|
|
43
|
+
- Press Tab and use up/down keys to select auto-complete items
|
|
44
44
|
## action/js commands
|
|
45
45
|
- A complete help is at the end
|
|
46
46
|
## Portability
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -101,6 +101,8 @@ const DEFAULT_SETTINGS = {
|
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
const LONG_LINE_REHIGHLIGHT_LIMIT = 300;
|
|
104
|
+
// Lines exceeding this are never highlighted interactively; stored as default and deferred to Esc.
|
|
105
|
+
const LONG_LINE_INITIAL_HIGHLIGHT_LIMIT = 10_000;
|
|
104
106
|
|
|
105
107
|
const promptHistory = new Map();
|
|
106
108
|
let startupHighlightProgress = null;
|
|
@@ -340,8 +342,11 @@ function normalizeCharBoundary(line, idx) {
|
|
|
340
342
|
// breaks[0] === 0 always. breaks[k] is the start of visual row k within `line`.
|
|
341
343
|
// Tabs are treated as `tabsize` columns wide (consistent with the renderer).
|
|
342
344
|
// With wordwrap=true, breaks at word boundaries; with wordwrap=false, hard-wraps at bufWidth.
|
|
345
|
+
let _swCacheLine = null, _swCacheBufWidth = 0, _swCacheWordwrap = false, _swCacheTabsize = 4, _swCacheBreaks = null;
|
|
343
346
|
function softwrapBreaks(line, bufWidth, wordwrap, tabsize) {
|
|
344
347
|
if (bufWidth <= 0) return [0];
|
|
348
|
+
if (line === _swCacheLine && bufWidth === _swCacheBufWidth && wordwrap === _swCacheWordwrap && tabsize === _swCacheTabsize)
|
|
349
|
+
return _swCacheBreaks;
|
|
345
350
|
const breaks = [0];
|
|
346
351
|
let visualX = 0; // display col within current visual row
|
|
347
352
|
let wordStart = 0; // code-unit index of current word start
|
|
@@ -381,6 +386,7 @@ function softwrapBreaks(line, bufWidth, wordwrap, tabsize) {
|
|
|
381
386
|
}
|
|
382
387
|
}
|
|
383
388
|
|
|
389
|
+
_swCacheLine = line; _swCacheBufWidth = bufWidth; _swCacheWordwrap = wordwrap; _swCacheTabsize = tabsize; _swCacheBreaks = breaks;
|
|
384
390
|
return breaks;
|
|
385
391
|
}
|
|
386
392
|
|
|
@@ -569,6 +575,9 @@ function parseInput(args) {
|
|
|
569
575
|
}
|
|
570
576
|
|
|
571
577
|
class BufferModel {
|
|
578
|
+
get searchPattern() { return this._searchPattern ?? ""; }
|
|
579
|
+
set searchPattern(v) { this._searchPattern = v ?? ""; this.searchMatches?.clear(); }
|
|
580
|
+
|
|
572
581
|
constructor({ path = "", text = "", command = {}, type = "default", readonly = false, modTimeMs = null, encoding = DEFAULT_SETTINGS.encoding } = {}) {
|
|
573
582
|
this.path = path;
|
|
574
583
|
this.type = type;
|
|
@@ -589,6 +598,7 @@ class BufferModel {
|
|
|
589
598
|
this.acSuggestions = [];
|
|
590
599
|
this.acCompletions = [];
|
|
591
600
|
this.acCurIdx = -1;
|
|
601
|
+
this.searchMatches = new Map();
|
|
592
602
|
this.searchPattern = "";
|
|
593
603
|
this.command = command;
|
|
594
604
|
this.filetype = "unknown";
|
|
@@ -660,6 +670,8 @@ class BufferModel {
|
|
|
660
670
|
|
|
661
671
|
invalidateHighlightFrom(lineNo = 0, options = {}) {
|
|
662
672
|
this._editRev = (this._editRev ?? 0) + 1;
|
|
673
|
+
if (options.force) this.searchMatches?.clear();
|
|
674
|
+
else this.searchMatches?.delete(lineNo);
|
|
663
675
|
invalidateHighlightFrom(this, lineNo, options);
|
|
664
676
|
}
|
|
665
677
|
|
|
@@ -2066,12 +2078,14 @@ class App {
|
|
|
2066
2078
|
const isCL = clBg && lineNo === buf.cursor.y && !pane.selection;
|
|
2067
2079
|
if (gutterW > 0) renderGutter(lineNo, row, screenRow);
|
|
2068
2080
|
if (lineNo < buf.lines.length) {
|
|
2069
|
-
const cells = renderHighlightedCells(buf, lineNo, buf.scroll.x, maxW, this.context.colorscheme, pane.selection, buf
|
|
2081
|
+
const cells = renderHighlightedCells(buf, lineNo, buf.scroll.x, maxW, this.context.colorscheme, pane.selection, getLineSearchRanges(buf, lineNo), braceMatches, isCL ? clBg : null);
|
|
2070
2082
|
putCells(this.screen, pane.x + gutterW, screenRow, cells, maxW);
|
|
2071
2083
|
}
|
|
2072
2084
|
}
|
|
2073
2085
|
} else {
|
|
2074
2086
|
let sloc = { line: buf.scroll.y, row: buf.scroll.row ?? 0 };
|
|
2087
|
+
let _swBreaksLineNo = -1, _swBreaks = null;
|
|
2088
|
+
let _swSearchLineNo = -1, _swSearchRanges = [];
|
|
2075
2089
|
for (let screenY = 0; screenY < pane.h; screenY++) {
|
|
2076
2090
|
const screenRow = pane.y + screenY;
|
|
2077
2091
|
const { line: lineNo, row: subRow } = sloc;
|
|
@@ -2079,13 +2093,15 @@ class App {
|
|
|
2079
2093
|
if (lineNo >= buf.lines.length) break;
|
|
2080
2094
|
|
|
2081
2095
|
const lineStr = buf.lines[lineNo] ?? "";
|
|
2082
|
-
|
|
2096
|
+
if (lineNo !== _swBreaksLineNo) { _swBreaks = softwrapBreaks(lineStr, maxW, wordwrap, tabsize); _swBreaksLineNo = lineNo; }
|
|
2097
|
+
if (lineNo !== _swSearchLineNo) { _swSearchRanges = getLineSearchRanges(buf, lineNo); _swSearchLineNo = lineNo; }
|
|
2098
|
+
const breaks = _swBreaks;
|
|
2083
2099
|
const segStart = breaks[subRow] ?? 0;
|
|
2084
2100
|
const isCL = clBg && lineNo === buf.cursor.y && !pane.selection;
|
|
2085
2101
|
|
|
2086
2102
|
if (gutterW > 0) renderGutter(lineNo, screenY, screenRow, subRow);
|
|
2087
2103
|
|
|
2088
|
-
const cells = renderHighlightedCells(buf, lineNo, segStart, maxW, this.context.colorscheme, pane.selection,
|
|
2104
|
+
const cells = renderHighlightedCells(buf, lineNo, segStart, maxW, this.context.colorscheme, pane.selection, _swSearchRanges, braceMatches, isCL ? clBg : null);
|
|
2089
2105
|
putCells(this.screen, pane.x + gutterW, screenRow, cells, maxW);
|
|
2090
2106
|
|
|
2091
2107
|
if (subRow + 1 < breaks.length) {
|
|
@@ -2535,8 +2551,8 @@ class App {
|
|
|
2535
2551
|
// Reset undo insert chain on any non-printable-char key
|
|
2536
2552
|
if (!(seq === text && text.length === 1 && text >= " ")) this._undoInsertChain = false;
|
|
2537
2553
|
|
|
2538
|
-
//
|
|
2539
|
-
if (
|
|
2554
|
+
// Keep autocomplete active while cycling candidates with Tab/Shift-Tab or Up/Down.
|
|
2555
|
+
if (!["tab", "backtab", "up", "down"].includes(seq) && buf?.acHas) buf.clearAutocomplete();
|
|
2540
2556
|
|
|
2541
2557
|
switch (seq) {
|
|
2542
2558
|
case "escape": {
|
|
@@ -2783,10 +2799,18 @@ class App {
|
|
|
2783
2799
|
buf.moveRight();
|
|
2784
2800
|
break;
|
|
2785
2801
|
case "up":
|
|
2802
|
+
if (buf.acHas) {
|
|
2803
|
+
buf.cycleAutocomplete(false);
|
|
2804
|
+
break;
|
|
2805
|
+
}
|
|
2786
2806
|
this.pane.selection = null;
|
|
2787
2807
|
this._moveUpVisual(buf, this.pane);
|
|
2788
2808
|
break;
|
|
2789
2809
|
case "down":
|
|
2810
|
+
if (buf.acHas) {
|
|
2811
|
+
buf.cycleAutocomplete(true);
|
|
2812
|
+
break;
|
|
2813
|
+
}
|
|
2790
2814
|
this.pane.selection = null;
|
|
2791
2815
|
this._moveDownVisual(buf, this.pane);
|
|
2792
2816
|
break;
|
|
@@ -4995,6 +5019,15 @@ function highlightBufferLine(buf, lineNo) {
|
|
|
4995
5019
|
if (!cache.forceLongLineRehighlight && cache.dirtyLongLines.has(y) && cache.results[y]) {
|
|
4996
5020
|
result = cache.results[y];
|
|
4997
5021
|
state = cache.states[y] ?? null;
|
|
5022
|
+
} else if (!cache.forceLongLineRehighlight && line.length > LONG_LINE_INITIAL_HIGHLIGHT_LIMIT) {
|
|
5023
|
+
// Too long to highlight interactively — store a default result and mark dirty for Esc rehighlight.
|
|
5024
|
+
if (!cache.results[y]) {
|
|
5025
|
+
cache.results[y] = { changes: new Map([[0, "default"], [line.length, "default"]]), state: null };
|
|
5026
|
+
cache.states[y] = null;
|
|
5027
|
+
}
|
|
5028
|
+
result = cache.results[y];
|
|
5029
|
+
state = null;
|
|
5030
|
+
cache.dirtyLongLines.add(y);
|
|
4998
5031
|
} else {
|
|
4999
5032
|
const progress = startupHighlightProgress
|
|
5000
5033
|
? (pos) => startupHighlightProgress.linePosition(pos, y, target)
|
|
@@ -5031,6 +5064,11 @@ function invalidateHighlightFrom(buf, lineNo = 0, { force = false } = {}) {
|
|
|
5031
5064
|
if (!cache) return;
|
|
5032
5065
|
const from = Math.max(0, Math.trunc(Number(lineNo) || 0));
|
|
5033
5066
|
const line = buf.lines[from] ?? "";
|
|
5067
|
+
// Hard limit: never clear cache for very long lines even on force — mark dirty instead.
|
|
5068
|
+
if (line.length > LONG_LINE_INITIAL_HIGHLIGHT_LIMIT && cache.results[from]) {
|
|
5069
|
+
cache.dirtyLongLines.add(from);
|
|
5070
|
+
return;
|
|
5071
|
+
}
|
|
5034
5072
|
if (!force && line.length > LONG_LINE_REHIGHLIGHT_LIMIT && cache.results[from]) {
|
|
5035
5073
|
cache.dirtyLongLines.add(from);
|
|
5036
5074
|
return;
|
|
@@ -5452,6 +5490,7 @@ function findMatchingBracePositions(buf) {
|
|
|
5452
5490
|
|
|
5453
5491
|
function findMatchingBracePair(buf) {
|
|
5454
5492
|
if (!(buf?.Settings?.matchbrace ?? DEFAULT_SETTINGS.matchbrace)) return null;
|
|
5493
|
+
if ((buf.lines[buf.cursor.y] ?? "").length > LONG_LINE_INITIAL_HIGHLIGHT_LIMIT) return null;
|
|
5455
5494
|
const left = braceAt(buf, buf.cursor.x - 1, buf.cursor.y);
|
|
5456
5495
|
const right = braceAt(buf, buf.cursor.x, buf.cursor.y);
|
|
5457
5496
|
let origin = null;
|
|
@@ -5509,7 +5548,7 @@ function braceKey(loc) {
|
|
|
5509
5548
|
return String(loc.y) + ":" + String(loc.x);
|
|
5510
5549
|
}
|
|
5511
5550
|
|
|
5512
|
-
function renderHighlightedCells(buf, lineNo, scrollX, maxWidth, colorscheme, selection = null,
|
|
5551
|
+
function renderHighlightedCells(buf, lineNo, scrollX, maxWidth, colorscheme, selection = null, searchRanges = [], braceMatches = null, cursorLineBg = null) {
|
|
5513
5552
|
const raw = buf.lines[lineNo] ?? "";
|
|
5514
5553
|
const cells = [];
|
|
5515
5554
|
let width = 0;
|
|
@@ -5520,8 +5559,6 @@ function renderHighlightedCells(buf, lineNo, scrollX, maxWidth, colorscheme, sel
|
|
|
5520
5559
|
if (changes.length === 0 || changes[0][0] !== 0) changes.unshift([0, "default"]);
|
|
5521
5560
|
changes.push([raw.length, changes.at(-1)?.[1] ?? "default"]);
|
|
5522
5561
|
}
|
|
5523
|
-
|
|
5524
|
-
const searchRanges = searchPattern ? getSearchRanges(raw, searchPattern, buf.Settings?.ignorecase ?? true) : [];
|
|
5525
5562
|
// Go: cursor-line bg is skipped when a syntax style already has a non-default background (preservebg)
|
|
5526
5563
|
const defBg = colorscheme?.defaultStyle?.bg ?? "default";
|
|
5527
5564
|
|
|
@@ -5537,6 +5574,7 @@ function renderHighlightedCells(buf, lineNo, scrollX, maxWidth, colorscheme, sel
|
|
|
5537
5574
|
: null;
|
|
5538
5575
|
|
|
5539
5576
|
let changeIndex = 0;
|
|
5577
|
+
let searchIdx = 0;
|
|
5540
5578
|
let i = scrollX;
|
|
5541
5579
|
while (i < raw.length && width < maxWidth) {
|
|
5542
5580
|
const cp = raw.codePointAt(i);
|
|
@@ -5550,7 +5588,8 @@ function renderHighlightedCells(buf, lineNo, scrollX, maxWidth, colorscheme, sel
|
|
|
5550
5588
|
const syntaxStyle = colorscheme?.get(group) ?? colorscheme?.defaultStyle ?? {};
|
|
5551
5589
|
const preservebg = cursorLineBg != null && syntaxStyle.bg !== undefined && syntaxStyle.bg !== defBg;
|
|
5552
5590
|
const baseStyle = (cursorLineBg && !preservebg) ? { ...syntaxStyle, bg: cursorLineBg } : syntaxStyle;
|
|
5553
|
-
|
|
5591
|
+
while (searchIdx < searchRanges.length && searchRanges[searchIdx][1] <= i) searchIdx++;
|
|
5592
|
+
const inSearch = searchIdx < searchRanges.length && i >= searchRanges[searchIdx][0] && i < searchRanges[searchIdx][1];
|
|
5554
5593
|
const selected = isSelected(selection, lineNo, i, i + charLen);
|
|
5555
5594
|
const braceMatched = braceMatches?.has(String(lineNo) + ":" + String(i));
|
|
5556
5595
|
let style = (showTrailingWs && i >= trailingWsIdx) ? trailingWsStyle : baseStyle;
|
|
@@ -5657,7 +5696,17 @@ function allMatchPositions(text, re, literal) {
|
|
|
5657
5696
|
return positions;
|
|
5658
5697
|
}
|
|
5659
5698
|
|
|
5660
|
-
function
|
|
5699
|
+
function getLineSearchRanges(buf, lineNo) {
|
|
5700
|
+
if (!buf.searchPattern) return [];
|
|
5701
|
+
if (!buf.searchMatches.has(lineNo)) {
|
|
5702
|
+
const raw = buf.lines[lineNo] ?? "";
|
|
5703
|
+
const ignoreCase = buf.Settings?.ignorecase ?? true;
|
|
5704
|
+
buf.searchMatches.set(lineNo, getSearchRanges(raw, buf.searchPattern, ignoreCase));
|
|
5705
|
+
}
|
|
5706
|
+
return buf.searchMatches.get(lineNo);
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5709
|
+
function getSearchRanges(line, pattern, ignoreCase = false, rangeStart = 0, rangeEnd = line.length) {
|
|
5661
5710
|
if (!pattern) return [];
|
|
5662
5711
|
let re;
|
|
5663
5712
|
try {
|
|
@@ -5667,16 +5716,18 @@ function getSearchRanges(line, pattern, ignoreCase = false) {
|
|
|
5667
5716
|
}
|
|
5668
5717
|
const ranges = [];
|
|
5669
5718
|
if (re) {
|
|
5719
|
+
re.lastIndex = rangeStart;
|
|
5670
5720
|
let m;
|
|
5671
5721
|
while ((m = re.exec(line)) !== null) {
|
|
5722
|
+
if (m.index >= rangeEnd) break;
|
|
5672
5723
|
if (m[0].length === 0) { re.lastIndex++; continue; }
|
|
5673
5724
|
ranges.push([m.index, m.index + m[0].length]);
|
|
5674
5725
|
}
|
|
5675
5726
|
} else {
|
|
5676
|
-
let idx =
|
|
5677
|
-
while (idx <
|
|
5727
|
+
let idx = rangeStart;
|
|
5728
|
+
while (idx < rangeEnd) {
|
|
5678
5729
|
const pos = line.indexOf(pattern, idx);
|
|
5679
|
-
if (pos < 0) break;
|
|
5730
|
+
if (pos < 0 || pos >= rangeEnd) break;
|
|
5680
5731
|
ranges.push([pos, pos + pattern.length]);
|
|
5681
5732
|
idx = pos + pattern.length;
|
|
5682
5733
|
}
|
package/todo.txt
CHANGED
|
@@ -68,6 +68,12 @@ Current handoff notes
|
|
|
68
68
|
- Lines over 300 chars keep their old cached highlight/state when dirtied, mark the row dirty, and defer full rehighlight until Esc.
|
|
69
69
|
- Dirty long lines render red in the gutter and in the statusline row field.
|
|
70
70
|
- Startup and Esc rehighlight show colorful bottom progress by highlighted character count, wrapped with Bun.wrapAnsi.
|
|
71
|
+
[x] Recent 0.9.x updates:
|
|
72
|
+
- Added hex3 encoding for binary edit/open/save paths and encoding completion.
|
|
73
|
+
- --cat/--bat-style highlighting supports HTTP/HTTPS URLs.
|
|
74
|
+
- Terminal pane close behavior improved: exited terminals show "Press enter to close" and Enter closes/restores the pane.
|
|
75
|
+
- Added Alt-s selection mode plus more Alt key help/defaultkey documentation.
|
|
76
|
+
- Added Dedent/Unindent action aliases for outdent actions.
|
|
71
77
|
[!] Known follow-up: syntax detection still needs Go parity. todo.txt can be misdetected as filetype B because signatures are considered globally instead of only disambiguating filename/header matches.
|
|
72
78
|
[!] Known tool note: apply_patch is currently unreliable in this environment; use Bun scripts for small file edits.
|
|
73
79
|
|
|
@@ -76,7 +82,9 @@ Screen / tcell parity
|
|
|
76
82
|
[x] Replace current whole-string renderer with CellBuffer-backed rendering and diff flush.
|
|
77
83
|
[x] Implement Screen.SetContent/GetContent/Fill/Show equivalents over CellBuffer.
|
|
78
84
|
[x] Preserve per-cell style and wide-char handling: Cell.filler for double-width right-half, Screen.Show skips filler cells, putText/putCells advance col by visual width, setFillerContent added to Screen/CellBuffer.
|
|
79
|
-
[
|
|
85
|
+
[~] Preserve combining chars (zero-width combining marks stored alongside base char).
|
|
86
|
+
Done: CellBuffer/Screen/VT100 store combining marks alongside the base cell and terminal pane rendering emits them through Screen.SetContent.
|
|
87
|
+
Remaining: editor text rendering still needs grapheme-cluster-level behavior for combining marks and ZWJ sequences.
|
|
80
88
|
[ ] Implement fake cursor and multi-cursor reverse styling behavior.
|
|
81
89
|
[ ] Add raw escape registration/unregistration equivalent to tcell RegisterRawSeq.
|
|
82
90
|
[~] Add complete bracketed paste event handling across split input chunks.
|
|
@@ -105,6 +113,7 @@ Buffer / editing model
|
|
|
105
113
|
[~] Implement save options: fileformat, encoding, eofnewline, rmtrailingws, mkparents, autosu/sucmd behavior.
|
|
106
114
|
Done: eofnewline save behavior exists; fileformat status/toggle exists; encoding decode/reopen supports Bun TextDecoder labels including common CJK encodings, and statusline encoding click pre-fills reopen with encoding completion.
|
|
107
115
|
Done: saving a non-UTF-8-decoded buffer prompts "Save in UTF-8?(y,n)" before converting the buffer to UTF-8 on disk.
|
|
116
|
+
Done: hex3 encoding supports binary edit/open/save paths without UTF-8 conversion prompt.
|
|
108
117
|
Remaining: non-UTF-8 save/encode, rmtrailingws, mkparents, autosu/sucmd, full fileformat behavior parity.
|
|
109
118
|
[ ] Implement backup recovery and permbackup behavior.
|
|
110
119
|
[~] Implement savecursor and saveundo serialization.
|
|
@@ -194,8 +203,8 @@ Lua plugin parity
|
|
|
194
203
|
Done: minimal Buf/BufPane/Cursor adapters support autoclose-style Line/Insert/Replace/cursor movement and option access.
|
|
195
204
|
Remaining: real BufPane/Cursor/Buffer object parity, selections, multi-cursor, Tab/TabList APIs, InfoPane/Log/Raw buffers.
|
|
196
205
|
[~] Implement all plugin lifecycle hooks: preinit, init, postinit, onRune, preInsertNewline, preBackspace, onSave, onBufferOpen, onBufferOptionChanged, onAnyEvent, etc.
|
|
197
|
-
Done: preinit/init/postinit, onRune, preInsertNewline, preBackspace, onSave, onBufferOpen, onSetActive/onBufferClose are dispatched in current editor paths.
|
|
198
|
-
Remaining:
|
|
206
|
+
Done: preinit/init/postinit, onRune, preInsertNewline, preBackspace, onSave, onBufferOpen, onSetActive/onBufferClose, onBufferOptionChanged are dispatched in current editor paths.
|
|
207
|
+
Remaining: onAnyEvent, deinit/reload hooks, full action hook coverage, exact args/return behavior.
|
|
199
208
|
[~] Ensure Lua return values can cancel operations where Go micro expects bool false.
|
|
200
209
|
Done: PluginManager.runBool treats false as cancellation and is wired for preBackspace/preInsertNewline.
|
|
201
210
|
Remaining: all cancellable actions/hooks need to use the same path and match Go micro semantics.
|
|
@@ -291,7 +300,9 @@ Shell / jobs / terminal pane
|
|
|
291
300
|
[x] Implement Ctrl-B ShellMode prompt and shell command execution with temporary screen fini/start behavior.
|
|
292
301
|
[x] Improve Bun PTY terminal pane rendering using terminal state/cell emulation instead of raw line log.
|
|
293
302
|
VT100 class in src/screen/vt100.js: CSI cursor/erase/SGR/scroll, CPR response, OSC strip.
|
|
294
|
-
[
|
|
303
|
+
[~] Implement terminal selection/copy and close behavior parity.
|
|
304
|
+
Done: exited terminal panes can be closed with Enter and restore the previous editor buffer when available.
|
|
305
|
+
Remaining: terminal selection/copy and full close behavior parity.
|
|
295
306
|
[ ] Verify Bun.spawn stdio usage everywhere; PTY terminal option remains separate.
|
|
296
307
|
[x] Shared HTTP backend in platform/commands.js: fetchHttp(url) and downloadFile(url, outPath).
|
|
297
308
|
Priority: Bun.which detects curl (curl -kL --silent --fail) or wget (wget --no-check-certificate -q -O -),
|