bunmicro 0.9.9 → 0.9.19
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 +19 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/runtime/jsplugins/example/example.js +3 -1
- package/src/config/clean.js +172 -0
- package/src/config/config.js +3 -1
- package/src/config/defaults.js +1 -1
- package/src/index.js +520 -152
- package/src/platform/clipboard.js +125 -7
- package/src/plugins/js-bridge.js +10 -8
- package/todo.txt +7 -3
package/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import { Config } from "./config/config.js";
|
|
10
10
|
import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./config/defaults.js";
|
|
11
|
+
import { cleanConfig } from "./config/clean.js";
|
|
11
12
|
import { RuntimeRegistry, RTColorscheme, RTHelp } from "./runtime/registry.js";
|
|
12
13
|
import { PluginManager } from "./plugins/manager.js";
|
|
13
14
|
import { JsPluginManager, buildMicroGlobal, runAction, listActions } from "./plugins/js-bridge.js";
|
|
@@ -17,8 +18,8 @@ import { Highlighter } from "./highlight/highlighter.js";
|
|
|
17
18
|
import { DISABLE_MOUSE, parseInputEvents, parseKey } from "./screen/events.js";
|
|
18
19
|
import { Screen } from "./screen/screen.js";
|
|
19
20
|
import { VT100 } from "./screen/vt100.js";
|
|
20
|
-
import { ClipboardManager } from "./platform/clipboard.js";
|
|
21
|
-
import { platformId, run as runCommand, runSync,
|
|
21
|
+
import { ClipboardManager, probeOSC52, osc52Clipboard } from "./platform/clipboard.js";
|
|
22
|
+
import { platformId, run as runCommand, runSync, fetchHttpBytes, detectHttpBackend } from "./platform/commands.js";
|
|
22
23
|
import { shellSplit } from "./shell/shell.js";
|
|
23
24
|
import { styleToAnsi } from "./display/ansi-style.js";
|
|
24
25
|
import { encodeBinaryToBuffer, decodeBinaryBytes } from "./buffer/fixed3-codec.js";
|
|
@@ -92,9 +93,11 @@ const DEFAULT_SETTINGS = {
|
|
|
92
93
|
savecursor: false,
|
|
93
94
|
softwrap: false,
|
|
94
95
|
wordwrap: false,
|
|
96
|
+
pageoverlap: 2,
|
|
95
97
|
scrollmargin: 3,
|
|
96
98
|
reload: "prompt",
|
|
97
99
|
encoding: "utf-8",
|
|
100
|
+
fileformat: process.platform === "win32" ? "dos" : "unix",
|
|
98
101
|
"comment.type": "",
|
|
99
102
|
commenttype: "",
|
|
100
103
|
trailingws: false,
|
|
@@ -136,8 +139,7 @@ function isHttpUrl(value) {
|
|
|
136
139
|
return String(value ?? "").startsWith("http://") || String(value ?? "").startsWith("https://");
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
const bytes = new Uint8Array(await Bun.file(path).arrayBuffer());
|
|
142
|
+
function decodeTextBytesWithEncoding(bytes, encoding = "utf-8") {
|
|
141
143
|
if (normalizeEncodingLabel(encoding) === "hex3") {
|
|
142
144
|
return { text: encodeBinaryToBuffer(bytes).toString("latin1"), encoding: "hex3" };
|
|
143
145
|
}
|
|
@@ -145,13 +147,14 @@ async function readTextFileWithEncoding(path, encoding = "utf-8") {
|
|
|
145
147
|
return { text: decoder.decode(bytes), encoding: decoder.encoding };
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
async function readTextFileWithEncoding(path, encoding = "utf-8") {
|
|
151
|
+
const bytes = new Uint8Array(await Bun.file(path).arrayBuffer());
|
|
152
|
+
return decodeTextBytesWithEncoding(bytes, encoding);
|
|
153
|
+
}
|
|
154
|
+
|
|
148
155
|
async function fetchTextWithEncoding(url, encoding = "utf-8") {
|
|
149
156
|
const bytes = await fetchHttpBytes(url);
|
|
150
|
-
|
|
151
|
-
return { text: encodeBinaryToBuffer(new Uint8Array(bytes)).toString("latin1"), encoding: "hex3" };
|
|
152
|
-
}
|
|
153
|
-
const decoder = new TextDecoder(normalizeEncodingLabel(encoding));
|
|
154
|
-
return { text: decoder.decode(bytes), encoding: decoder.encoding };
|
|
157
|
+
return decodeTextBytesWithEncoding(new Uint8Array(bytes), encoding);
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
function normalizeEncodingLabel(encoding = "utf-8") {
|
|
@@ -160,6 +163,21 @@ function normalizeEncodingLabel(encoding = "utf-8") {
|
|
|
160
163
|
return new TextDecoder(s).encoding;
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
function detectFileFormat(text, fallback = DEFAULT_SETTINGS.fileformat) {
|
|
167
|
+
if (text.length === 0) return fallback === "dos" ? "dos" : "unix";
|
|
168
|
+
const newlineIdx = text.indexOf("\n");
|
|
169
|
+
if (newlineIdx < 0) return "unix";
|
|
170
|
+
return newlineIdx > 0 && text.charCodeAt(newlineIdx - 1) === 13 ? "dos" : "unix";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function normalizeBufferText(text) {
|
|
174
|
+
return String(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function encodeBufferTextForFile(text, fileformat) {
|
|
178
|
+
return fileformat === "dos" ? text.replace(/\n/g, "\r\n") : text;
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
function isReadonlyBuffer(buf) {
|
|
164
182
|
return Boolean(buf?.readonly || buf?.Settings?.readonly || buf?.Type?.Readonly);
|
|
165
183
|
}
|
|
@@ -469,6 +487,7 @@ function parseArgs(argv) {
|
|
|
469
487
|
help: false,
|
|
470
488
|
clean: false,
|
|
471
489
|
cat: false,
|
|
490
|
+
docs: false,
|
|
472
491
|
configDir: "",
|
|
473
492
|
debug: false,
|
|
474
493
|
profile: false,
|
|
@@ -484,6 +503,7 @@ function parseArgs(argv) {
|
|
|
484
503
|
else if (arg === "-help" || arg === "--help" || arg === "-h") flags.help = true;
|
|
485
504
|
else if (arg === "-clean") flags.clean = true;
|
|
486
505
|
else if (arg === "--cat" || arg === "-cat" || arg === "--ccat" || arg === "-ccat" || arg === "--bat" || arg === "-bat" || arg === "--glow" || arg === "-glow") flags.cat = true;
|
|
506
|
+
else if (arg === "--docs" || arg === "--readme") flags.docs = true;
|
|
487
507
|
else if (arg === "-debug") flags.debug = true;
|
|
488
508
|
else if (arg === "-profile") flags.profile = true;
|
|
489
509
|
else if (arg === "-config-dir") flags.configDir = argv[++i] ?? "";
|
|
@@ -500,17 +520,13 @@ function parseArgs(argv) {
|
|
|
500
520
|
|
|
501
521
|
function usage() {
|
|
502
522
|
return [
|
|
503
|
-
`Usage:
|
|
523
|
+
`Usage:
|
|
524
|
+
${pkg.name} [OPTIONs] [FILEs] [+line[.subrow][:col]]\n`,
|
|
504
525
|
"-clean",
|
|
505
|
-
" Clean configuration directory and exit
|
|
526
|
+
" Clean configuration directory and exit",
|
|
506
527
|
"-config-dir dir",
|
|
507
528
|
" Specify a custom location for configuration directory",
|
|
508
|
-
"
|
|
509
|
-
" Enable debug logging",
|
|
510
|
-
"-help, --help, -h",
|
|
511
|
-
" Show this help and exit",
|
|
512
|
-
"-options",
|
|
513
|
-
" Show option help and exit",
|
|
529
|
+
"",
|
|
514
530
|
"-plugin list",
|
|
515
531
|
" List installed plugins",
|
|
516
532
|
"-plugin available|avail",
|
|
@@ -523,20 +539,28 @@ function usage() {
|
|
|
523
539
|
" Remove installed plugin(s)",
|
|
524
540
|
"-plugin update [name]...",
|
|
525
541
|
" Update installed plugin(s) (all if no name given)",
|
|
526
|
-
|
|
527
|
-
"-version, -V",
|
|
528
|
-
" Show version number and information and exit",
|
|
529
|
-
"--cat, --ccat, --bat, --glow",
|
|
530
|
-
" Syntax-highlight file(s) and write to stdout, then exit (.md uses Bun.markdown.ansi)",
|
|
542
|
+
"",
|
|
531
543
|
"-<option> value",
|
|
532
544
|
" Set an option for this session",
|
|
545
|
+
"-options",
|
|
546
|
+
" Show option help and exit\n",
|
|
547
|
+
"--cat, --ccat, --bat, --glow",
|
|
548
|
+
" Syntax-highlight file(s) and write to stdout, then exit (.md uses Bun.markdown.ansi)\n",
|
|
549
|
+
"-help, -h, --help",
|
|
550
|
+
" Show this help & exit",
|
|
551
|
+
"-version, -V, --version",
|
|
552
|
+
" Show version+backend info & exit",
|
|
553
|
+
"--docs, --readme",
|
|
554
|
+
` Show ${pkg.name}'s README.md & exit`,
|
|
555
|
+
|
|
556
|
+
|
|
533
557
|
].join("\n");
|
|
534
558
|
}
|
|
535
559
|
|
|
536
560
|
function parseInput(args) {
|
|
537
561
|
const files = [];
|
|
538
562
|
const command = {
|
|
539
|
-
startCursor: {
|
|
563
|
+
startCursor: { line: -1, subRow: 0, col: 1 },
|
|
540
564
|
searchRegex: "",
|
|
541
565
|
searchAfterStart: false,
|
|
542
566
|
};
|
|
@@ -545,25 +569,19 @@ function parseInput(args) {
|
|
|
545
569
|
|
|
546
570
|
for (let i = 0; i < args.length; i++) {
|
|
547
571
|
const arg = args[i];
|
|
548
|
-
const pos = arg.match(/^\+(
|
|
572
|
+
const pos = arg.match(/^\+(-?\d+(?:\.\d+)?)(?::(-?\d+))?$/);
|
|
549
573
|
const search = arg.match(/^\+\/(.+)$/);
|
|
550
|
-
const cursorFile = arg.match(/^(.+):(
|
|
574
|
+
const cursorFile = arg.match(/^(.+):(-?\d+(?:\.\d+)?)(?::(-?\d+))?$/);
|
|
551
575
|
|
|
552
576
|
if (pos) {
|
|
553
|
-
command.startCursor = {
|
|
554
|
-
x: pos[2] ? Number(pos[2]) - 1 : 0,
|
|
555
|
-
y: Number(pos[1]) - 1,
|
|
556
|
-
};
|
|
577
|
+
command.startCursor = parseLineCol(`${pos[1]}${pos[2] ? `:${pos[2]}` : ""}`);
|
|
557
578
|
posIndex = i;
|
|
558
579
|
} else if (search) {
|
|
559
580
|
command.searchRegex = search[1];
|
|
560
581
|
searchIndex = i;
|
|
561
582
|
} else if (DEFAULT_SETTINGS.parsecursor && cursorFile && existsSync(cursorFile[1])) {
|
|
562
583
|
files.push(cursorFile[1]);
|
|
563
|
-
command.startCursor = {
|
|
564
|
-
x: cursorFile[3] ? Number(cursorFile[3]) - 1 : 0,
|
|
565
|
-
y: Number(cursorFile[2]) - 1,
|
|
566
|
-
};
|
|
584
|
+
command.startCursor = parseLineCol(`${cursorFile[2]}${cursorFile[3] ? `:${cursorFile[3]}` : ""}`);
|
|
567
585
|
posIndex = i;
|
|
568
586
|
} else {
|
|
569
587
|
files.push(arg);
|
|
@@ -582,9 +600,9 @@ class BufferModel {
|
|
|
582
600
|
this.path = path;
|
|
583
601
|
this.type = type;
|
|
584
602
|
this.name = path ? basename(path) : "No name";
|
|
585
|
-
this.fileformat = text.
|
|
603
|
+
this.fileformat = detectFileFormat(text, DEFAULT_SETTINGS.fileformat);
|
|
586
604
|
this.encoding = normalizeEncodingLabel(encoding);
|
|
587
|
-
this.lines = text
|
|
605
|
+
this.lines = normalizeBufferText(text).split("\n");
|
|
588
606
|
if (this.lines.length === 0) this.lines = [""];
|
|
589
607
|
this.cursor = { x: 0, y: 0 };
|
|
590
608
|
this.scroll = { x: 0, y: 0, row: 0 };
|
|
@@ -623,8 +641,11 @@ class BufferModel {
|
|
|
623
641
|
savecursor: DEFAULT_SETTINGS.savecursor,
|
|
624
642
|
softwrap: DEFAULT_SETTINGS.softwrap,
|
|
625
643
|
wordwrap: DEFAULT_SETTINGS.wordwrap,
|
|
644
|
+
pageoverlap: DEFAULT_SETTINGS.pageoverlap,
|
|
626
645
|
scrollmargin: DEFAULT_SETTINGS.scrollmargin,
|
|
627
646
|
reload: DEFAULT_SETTINGS.reload,
|
|
647
|
+
eofnewline: DEFAULT_SETTINGS.eofnewline,
|
|
648
|
+
fileformat: this.fileformat,
|
|
628
649
|
trailingws: DEFAULT_SETTINGS.trailingws,
|
|
629
650
|
encoding: this.encoding,
|
|
630
651
|
readonly,
|
|
@@ -633,9 +654,13 @@ class BufferModel {
|
|
|
633
654
|
this.AbsPath = path;
|
|
634
655
|
this.Type = { Scratch: type !== "default", Kind: 0, Readonly: readonly };
|
|
635
656
|
|
|
636
|
-
if (command
|
|
637
|
-
|
|
638
|
-
|
|
657
|
+
if (commandHasStartCursor(command)) {
|
|
658
|
+
if (command.startCursor.subRow > 0) {
|
|
659
|
+
this.gotoLoc(command.startCursor.line, 1);
|
|
660
|
+
this._pendingVisualGoto = { subRow: command.startCursor.subRow, col: command.startCursor.col };
|
|
661
|
+
} else {
|
|
662
|
+
this.gotoLoc(command.startCursor.line, command.startCursor.col);
|
|
663
|
+
}
|
|
639
664
|
}
|
|
640
665
|
if (command.searchRegex) this.search(command.searchRegex, command.searchAfterStart);
|
|
641
666
|
}
|
|
@@ -897,8 +922,8 @@ class BufferModel {
|
|
|
897
922
|
return true;
|
|
898
923
|
}
|
|
899
924
|
|
|
900
|
-
page(delta,
|
|
901
|
-
this.cursor.y += delta * Math.max(1,
|
|
925
|
+
page(delta, amount) {
|
|
926
|
+
this.cursor.y += delta * Math.max(1, amount);
|
|
902
927
|
this.ensureCursor();
|
|
903
928
|
}
|
|
904
929
|
|
|
@@ -1019,8 +1044,9 @@ class BufferModel {
|
|
|
1019
1044
|
const text = decoded.text;
|
|
1020
1045
|
this.encoding = decoded.encoding;
|
|
1021
1046
|
this.Settings.encoding = decoded.encoding;
|
|
1022
|
-
this.fileformat = text.
|
|
1023
|
-
this.
|
|
1047
|
+
this.fileformat = detectFileFormat(text, this.Settings.fileformat ?? DEFAULT_SETTINGS.fileformat);
|
|
1048
|
+
this.Settings.fileformat = this.fileformat;
|
|
1049
|
+
this.lines = normalizeBufferText(text).split("\n");
|
|
1024
1050
|
if (this.lines.length === 0) this.lines = [""];
|
|
1025
1051
|
this.modTimeMs = null;
|
|
1026
1052
|
this.readonly = false;
|
|
@@ -1044,8 +1070,9 @@ class BufferModel {
|
|
|
1044
1070
|
const text = decoded.text;
|
|
1045
1071
|
this.encoding = decoded.encoding;
|
|
1046
1072
|
this.Settings.encoding = decoded.encoding;
|
|
1047
|
-
this.fileformat = text.
|
|
1048
|
-
this.
|
|
1073
|
+
this.fileformat = detectFileFormat(text, this.Settings.fileformat ?? DEFAULT_SETTINGS.fileformat);
|
|
1074
|
+
this.Settings.fileformat = this.fileformat;
|
|
1075
|
+
this.lines = normalizeBufferText(text).split("\n");
|
|
1049
1076
|
if (this.lines.length === 0) this.lines = [""];
|
|
1050
1077
|
this.modTimeMs = info.mtimeMs;
|
|
1051
1078
|
this.readonly = !canWritePath(this.path);
|
|
@@ -1080,8 +1107,8 @@ class BufferModel {
|
|
|
1080
1107
|
this.message = `Saved ${path}`;
|
|
1081
1108
|
return;
|
|
1082
1109
|
}
|
|
1083
|
-
if (DEFAULT_SETTINGS.eofnewline && !text.endsWith("\n")) text += "\n";
|
|
1084
|
-
await Bun.write(path, text);
|
|
1110
|
+
if ((this.Settings.eofnewline ?? DEFAULT_SETTINGS.eofnewline) && !text.endsWith("\n")) text += "\n";
|
|
1111
|
+
await Bun.write(path, encodeBufferTextForFile(text, this.Settings.fileformat ?? this.fileformat));
|
|
1085
1112
|
this.encoding = "utf-8";
|
|
1086
1113
|
this.Settings.encoding = "utf-8";
|
|
1087
1114
|
this.path = path;
|
|
@@ -1222,19 +1249,27 @@ class BufferModel {
|
|
|
1222
1249
|
SetOption(option, value) {
|
|
1223
1250
|
const oldValue = this.Settings[option];
|
|
1224
1251
|
const parsed = parseOptionValue(value);
|
|
1225
|
-
|
|
1252
|
+
if (option === "fileformat" && !OPTION_CHOICES.fileformat.includes(String(parsed))) {
|
|
1253
|
+
throw new Error(`Invalid value for fileformat: ${parsed}`);
|
|
1254
|
+
}
|
|
1255
|
+
this.Settings[option] = option === "encoding" ? normalizeEncodingLabel(parsed) : option === "fileformat" ? String(parsed) : parsed;
|
|
1226
1256
|
if (option === "filetype") this.filetype = String(parsed);
|
|
1227
1257
|
if (option === "encoding") this.encoding = this.Settings.encoding;
|
|
1258
|
+
if (option === "fileformat") this.fileformat = this.Settings.fileformat === "dos" ? "dos" : "unix";
|
|
1228
1259
|
if (option === "readonly") { this.readonly = Boolean(parsed); this.Type.Readonly = this.readonly; }
|
|
1229
|
-
if (option in DEFAULT_SETTINGS) DEFAULT_SETTINGS[option] = this.Settings[option];
|
|
1260
|
+
if (option in DEFAULT_SETTINGS && option !== "fileformat") DEFAULT_SETTINGS[option] = this.Settings[option];
|
|
1230
1261
|
this._onOptionChange?.(option, oldValue, this.Settings[option]);
|
|
1231
1262
|
}
|
|
1232
1263
|
|
|
1233
1264
|
DoSetOptionNative(option, value) {
|
|
1234
1265
|
const oldValue = this.Settings[option];
|
|
1235
|
-
|
|
1266
|
+
if (option === "fileformat" && !OPTION_CHOICES.fileformat.includes(String(value))) {
|
|
1267
|
+
throw new Error(`Invalid value for fileformat: ${value}`);
|
|
1268
|
+
}
|
|
1269
|
+
this.Settings[option] = option === "encoding" ? normalizeEncodingLabel(value) : option === "fileformat" ? String(value) : value;
|
|
1236
1270
|
if (option === "filetype") this.filetype = String(value);
|
|
1237
1271
|
if (option === "encoding") this.encoding = this.Settings.encoding;
|
|
1272
|
+
if (option === "fileformat") this.fileformat = this.Settings.fileformat === "dos" ? "dos" : "unix";
|
|
1238
1273
|
if (option === "readonly") { this.readonly = Boolean(value); this.Type.Readonly = this.readonly; }
|
|
1239
1274
|
this._onOptionChange?.(option, oldValue, this.Settings[option]);
|
|
1240
1275
|
}
|
|
@@ -1263,7 +1298,7 @@ class BufferModel {
|
|
|
1263
1298
|
}
|
|
1264
1299
|
|
|
1265
1300
|
Bytes() {
|
|
1266
|
-
return this.lines.join("\n");
|
|
1301
|
+
return encodeBufferTextForFile(this.lines.join("\n"), this.Settings.fileformat ?? this.fileformat);
|
|
1267
1302
|
}
|
|
1268
1303
|
|
|
1269
1304
|
Size() {
|
|
@@ -1636,6 +1671,10 @@ class App {
|
|
|
1636
1671
|
this._acHScroll = 0;
|
|
1637
1672
|
this._suppressMouseUntilUp = false;
|
|
1638
1673
|
this._undoInsertChain = false;
|
|
1674
|
+
this._freshClip = false;
|
|
1675
|
+
this._messageClickAction = null;
|
|
1676
|
+
this._messageRowY = null;
|
|
1677
|
+
this._messageRowClickZone = null;
|
|
1639
1678
|
}
|
|
1640
1679
|
|
|
1641
1680
|
get tab() { return this.tabs[this.activeTabIdx]; }
|
|
@@ -1645,6 +1684,22 @@ class App {
|
|
|
1645
1684
|
get active() { return this.activeTabIdx; }
|
|
1646
1685
|
get buffers() { return this.tabs.map(t => t.buffer).filter(Boolean); }
|
|
1647
1686
|
|
|
1687
|
+
paneForBuffer(buffer) {
|
|
1688
|
+
for (const tab of this.tabs) {
|
|
1689
|
+
const pane = tab.panes().find((p) => p.buffer === buffer);
|
|
1690
|
+
if (pane) return pane;
|
|
1691
|
+
}
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
formatCursorLocation(buffer = this.buffer, pane = null) {
|
|
1696
|
+
return formatCursorLocation(buffer, pane ?? this.paneForBuffer(buffer) ?? this.pane);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
formatAbsoluteCursorLocation(buffer = this.buffer) {
|
|
1700
|
+
return formatAbsoluteCursorLocation(buffer);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1648
1703
|
async start() {
|
|
1649
1704
|
this._started = true;
|
|
1650
1705
|
// When stdin was a pipe (content already consumed in loadBuffers), open the
|
|
@@ -1666,7 +1721,10 @@ class App {
|
|
|
1666
1721
|
_activeTtyStream = this._ttyStream;
|
|
1667
1722
|
this._ttyStream.setRawMode?.(true);
|
|
1668
1723
|
this._ttyStream.resume();
|
|
1669
|
-
this.
|
|
1724
|
+
const clipSetting = this.context?.config?.getGlobalOption("clipboard") ?? "external";
|
|
1725
|
+
await this.reinitializeClipboard(clipSetting);
|
|
1726
|
+
this._inputHandler = (data) => this.handleInput(data);
|
|
1727
|
+
this._ttyStream.on("data", this._inputHandler);
|
|
1670
1728
|
process.stdout.on("resize", () => {
|
|
1671
1729
|
const resize = this.screen.updateSize();
|
|
1672
1730
|
this.rows = resize.rows;
|
|
@@ -1687,6 +1745,15 @@ class App {
|
|
|
1687
1745
|
}
|
|
1688
1746
|
}
|
|
1689
1747
|
|
|
1748
|
+
async reinitializeClipboard(setting) {
|
|
1749
|
+
if (this._inputHandler) this._ttyStream?.removeListener("data", this._inputHandler);
|
|
1750
|
+
try {
|
|
1751
|
+
await this.clipboard.initFromSetting(setting, this._ttyStream, process.stdout, 150);
|
|
1752
|
+
} finally {
|
|
1753
|
+
if (this._inputHandler) this._ttyStream?.on("data", this._inputHandler);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1690
1757
|
async stop(code = 0) {
|
|
1691
1758
|
this.running = false;
|
|
1692
1759
|
for (const tab of this.tabs)
|
|
@@ -1912,7 +1979,23 @@ class App {
|
|
|
1912
1979
|
const statusStyle = this.context.colorscheme?.get("statusline") ?? { ...defaultStyle, reverse: true };
|
|
1913
1980
|
const style = { ...statusStyle, reverse: false };
|
|
1914
1981
|
putText(this.screen, 0, row, " ".repeat(this.cols), style, this.cols);
|
|
1915
|
-
|
|
1982
|
+
this._messageRowY = row;
|
|
1983
|
+
this._messageRowClickZone = null;
|
|
1984
|
+
const msg = String(message);
|
|
1985
|
+
// detect [AltMethod] prefix — render it underlined as a clickable button
|
|
1986
|
+
if (this._messageClickAction && msg.startsWith("[")) {
|
|
1987
|
+
const close = msg.indexOf("]");
|
|
1988
|
+
if (close > 0) {
|
|
1989
|
+
const btnText = msg.slice(0, close + 1);
|
|
1990
|
+
const rest = msg.slice(close + 1);
|
|
1991
|
+
const btnStyle = { ...style, underline: true };
|
|
1992
|
+
let sx = putText(this.screen, 0, row, btnText, btnStyle, this.cols);
|
|
1993
|
+
putText(this.screen, sx, row, rest.slice(0, this.cols - sx), style, this.cols - sx);
|
|
1994
|
+
this._messageRowClickZone = { start: 0, end: sx };
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
putText(this.screen, 0, row, msg.slice(0, this.cols), style, this.cols);
|
|
1916
1999
|
}
|
|
1917
2000
|
|
|
1918
2001
|
renderKeyMenu(defaultStyle, statusRow) {
|
|
@@ -2008,9 +2091,46 @@ class App {
|
|
|
2008
2091
|
return -1;
|
|
2009
2092
|
}
|
|
2010
2093
|
|
|
2094
|
+
gotoLocation(buf, loc, pane = this.pane) {
|
|
2095
|
+
if (!buf) return;
|
|
2096
|
+
if (!loc?.subRow) {
|
|
2097
|
+
buf.gotoLoc(loc.line, loc.col);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
buf.gotoLoc(loc.line, 1);
|
|
2101
|
+
this.applyVisualGoto(buf, pane, loc.subRow, loc.col);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
applyVisualGoto(buf, pane, subRow, col = 1) {
|
|
2105
|
+
if (!buf || !pane) return;
|
|
2106
|
+
const softwrap = buf.Settings?.softwrap ?? false;
|
|
2107
|
+
if (!softwrap) {
|
|
2108
|
+
buf.gotoLoc(buf.cursor.y + 1, col);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const gutterW = editorGutterWidth(buf);
|
|
2112
|
+
const bufW = Math.max(1, pane.w - gutterW);
|
|
2113
|
+
const wordwrap = buf.Settings?.wordwrap ?? false;
|
|
2114
|
+
const tabsize = buf.Settings?.tabsize ?? DEFAULT_SETTINGS.tabsize;
|
|
2115
|
+
const line = buf.lines[buf.cursor.y] ?? "";
|
|
2116
|
+
const breaks = softwrapBreaks(line, bufW, wordwrap, tabsize);
|
|
2117
|
+
const targetSubRow = clamp(Math.trunc(Number(subRow) || 0), 0, Math.max(0, breaks.length - 1));
|
|
2118
|
+
const segStart = breaks[targetSubRow] ?? 0;
|
|
2119
|
+
buf.cursor.x = visualColToCharIdx(line, segStart, Math.max(0, Math.trunc(Number(col) || 1) - 1));
|
|
2120
|
+
buf.ensureCursor();
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
applyPendingVisualGoto(pane) {
|
|
2124
|
+
const pending = pane?.buffer?._pendingVisualGoto;
|
|
2125
|
+
if (!pending) return;
|
|
2126
|
+
delete pane.buffer._pendingVisualGoto;
|
|
2127
|
+
this.applyVisualGoto(pane.buffer, pane, pending.subRow, pending.col);
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2011
2130
|
renderEditorPane(pane, defaultStyle) {
|
|
2012
2131
|
const buf = pane.buffer;
|
|
2013
2132
|
if (!buf) return;
|
|
2133
|
+
this.applyPendingVisualGoto(pane);
|
|
2014
2134
|
this.updateScrollForPane(pane);
|
|
2015
2135
|
const gutterW = editorGutterWidth(buf);
|
|
2016
2136
|
const braceMatches = findMatchingBracePositions(buf);
|
|
@@ -2066,7 +2186,7 @@ class App {
|
|
|
2066
2186
|
if (lineNumW > 0) {
|
|
2067
2187
|
const prefix = subRow === 0
|
|
2068
2188
|
? lineNumberText(buf, lineNo, row, lineNumW)
|
|
2069
|
-
:
|
|
2189
|
+
: visualLineNumberText(subRow, lineNumW);
|
|
2070
2190
|
putText(this.screen, pane.x + msgW + diffCol, screenRow, prefix, isDirtyLongLine(buf, lineNo) ? dirtyGutterStyle : gutterStyle, lineNumW);
|
|
2071
2191
|
}
|
|
2072
2192
|
};
|
|
@@ -2287,6 +2407,86 @@ class App {
|
|
|
2287
2407
|
buf.scroll.x = charIdxForScrollRight(buf.lines[buf.cursor.y] ?? "", buf.cursor.x, bufW);
|
|
2288
2408
|
}
|
|
2289
2409
|
}
|
|
2410
|
+
|
|
2411
|
+
pageScroll(pane, delta, amount = null) {
|
|
2412
|
+
const buf = pane?.buffer;
|
|
2413
|
+
if (!buf) return;
|
|
2414
|
+
const gutterW = editorGutterWidth(buf);
|
|
2415
|
+
const bufW = Math.max(1, (pane?.w ?? this.cols) - gutterW);
|
|
2416
|
+
const softwrap = buf.Settings?.softwrap ?? false;
|
|
2417
|
+
const wordwrap = softwrap && (buf.Settings?.wordwrap ?? false);
|
|
2418
|
+
const tabsize = buf.Settings?.tabsize ?? DEFAULT_SETTINGS.tabsize;
|
|
2419
|
+
const pageOverlap = Math.trunc(Number(buf.Settings?.pageoverlap ?? DEFAULT_SETTINGS.pageoverlap) || 0);
|
|
2420
|
+
const scrollAmount = amount ?? Math.max(1, (pane?.h ?? this.rows) - pageOverlap);
|
|
2421
|
+
|
|
2422
|
+
if (softwrap) {
|
|
2423
|
+
const start = { line: buf.scroll.y, row: buf.scroll.row ?? 0 };
|
|
2424
|
+
const next = delta < 0
|
|
2425
|
+
? slocRetreatN(buf.lines, start, scrollAmount, bufW, wordwrap, tabsize)
|
|
2426
|
+
: slocAdvanceN(buf.lines, start, scrollAmount, bufW, wordwrap, tabsize);
|
|
2427
|
+
buf.scroll.y = next.line;
|
|
2428
|
+
buf.scroll.row = next.row;
|
|
2429
|
+
buf.scroll.x = 0;
|
|
2430
|
+
if (delta > 0) this.scrollAdjust(pane);
|
|
2431
|
+
} else {
|
|
2432
|
+
buf.scroll.y = Math.max(0, (buf.scroll.y ?? 0) + delta * scrollAmount);
|
|
2433
|
+
buf.scroll.row = 0;
|
|
2434
|
+
if (delta > 0) this.scrollAdjust(pane);
|
|
2435
|
+
}
|
|
2436
|
+
buf.allowCursorOffscreen = true;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
scrollAdjust(pane) {
|
|
2440
|
+
const buf = pane?.buffer;
|
|
2441
|
+
if (!buf || buf.lines.length === 0) return;
|
|
2442
|
+
const gutterW = editorGutterWidth(buf);
|
|
2443
|
+
const bufW = Math.max(1, (pane?.w ?? this.cols) - gutterW);
|
|
2444
|
+
const softwrap = buf.Settings?.softwrap ?? false;
|
|
2445
|
+
const wordwrap = softwrap && (buf.Settings?.wordwrap ?? false);
|
|
2446
|
+
const tabsize = buf.Settings?.tabsize ?? DEFAULT_SETTINGS.tabsize;
|
|
2447
|
+
if (softwrap) {
|
|
2448
|
+
const endLine = Math.max(0, buf.lines.length - 1);
|
|
2449
|
+
const endBreaks = softwrapBreaks(buf.lines[endLine] ?? "", bufW, wordwrap, tabsize);
|
|
2450
|
+
const end = { line: endLine, row: Math.max(0, endBreaks.length - 1) };
|
|
2451
|
+
const start = { line: buf.scroll.y, row: buf.scroll.row ?? 0 };
|
|
2452
|
+
if (slocDiff(buf.lines, start, end, bufW, wordwrap, tabsize) < (pane?.h ?? this.rows) - 1) {
|
|
2453
|
+
const adjusted = slocRetreatN(buf.lines, end, Math.max(0, (pane?.h ?? this.rows) - 1), bufW, wordwrap, tabsize);
|
|
2454
|
+
buf.scroll.y = adjusted.line;
|
|
2455
|
+
buf.scroll.row = adjusted.row;
|
|
2456
|
+
}
|
|
2457
|
+
} else {
|
|
2458
|
+
buf.scroll.y = Math.min(buf.scroll.y ?? 0, Math.max(0, buf.lines.length - (pane?.h ?? this.rows)));
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
cursorPage(pane, delta, { select = false, amount = null } = {}) {
|
|
2463
|
+
const buf = pane?.buffer;
|
|
2464
|
+
if (!buf) return;
|
|
2465
|
+
const pageOverlap = Math.trunc(Number(buf.Settings?.pageoverlap ?? DEFAULT_SETTINGS.pageoverlap) || 0);
|
|
2466
|
+
const selectionEndNewline = !select && delta > 0 && pane.selection?.end?.x === 0;
|
|
2467
|
+
let scrollAmount = amount ?? Math.max(1, (pane?.h ?? this.rows) - pageOverlap);
|
|
2468
|
+
if (selectionEndNewline) scrollAmount = Math.max(1, scrollAmount - 1);
|
|
2469
|
+
const move = () => {
|
|
2470
|
+
const softwrap = buf.Settings?.softwrap ?? false;
|
|
2471
|
+
if (softwrap) {
|
|
2472
|
+
for (let i = 0; i < scrollAmount; i++) {
|
|
2473
|
+
if (delta < 0) this._moveUpVisual(buf, pane);
|
|
2474
|
+
else this._moveDownVisual(buf, pane);
|
|
2475
|
+
}
|
|
2476
|
+
} else {
|
|
2477
|
+
buf.page(delta, scrollAmount);
|
|
2478
|
+
}
|
|
2479
|
+
};
|
|
2480
|
+
if (select) extendSelection(pane, buf, move);
|
|
2481
|
+
else {
|
|
2482
|
+
pane.selection = null;
|
|
2483
|
+
move();
|
|
2484
|
+
}
|
|
2485
|
+
if (selectionEndNewline) buf.moveHome();
|
|
2486
|
+
this.pageScroll(pane, delta, scrollAmount);
|
|
2487
|
+
buf.allowCursorOffscreen = false;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2290
2490
|
// Softwrap-aware vertical cursor movement.
|
|
2291
2491
|
// Moves cursor by one visual row, maintaining the target visual X column.
|
|
2292
2492
|
_softwrapGetContext(buf, pane) {
|
|
@@ -2395,6 +2595,12 @@ class App {
|
|
|
2395
2595
|
async _dispatchInput(data) {
|
|
2396
2596
|
const text = decoder.decode(data);
|
|
2397
2597
|
|
|
2598
|
+
// Any non-mouse input clears the clipboard alt-copy action
|
|
2599
|
+
{
|
|
2600
|
+
const _evts = parseInputEvents(data);
|
|
2601
|
+
if (_evts.some(e => e.type !== "mouse")) this._messageClickAction = null;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2398
2604
|
// Any non-mouse input stops TTS
|
|
2399
2605
|
if (this._ttsState) {
|
|
2400
2606
|
const events = parseInputEvents(data);
|
|
@@ -2504,12 +2710,14 @@ class App {
|
|
|
2504
2710
|
else if (event.type === "paste") await this.handlePrompt(event.text);
|
|
2505
2711
|
else await this.handleEvent(event);
|
|
2506
2712
|
}
|
|
2713
|
+
this._syncPrimarySelection();
|
|
2507
2714
|
return;
|
|
2508
2715
|
}
|
|
2509
2716
|
|
|
2510
2717
|
for (const event of parseInputEvents(data)) {
|
|
2511
2718
|
await this.handleEvent(event);
|
|
2512
2719
|
}
|
|
2720
|
+
this._syncPrimarySelection();
|
|
2513
2721
|
}
|
|
2514
2722
|
|
|
2515
2723
|
async handleEvent(event) {
|
|
@@ -2551,8 +2759,8 @@ class App {
|
|
|
2551
2759
|
// Reset undo insert chain on any non-printable-char key
|
|
2552
2760
|
if (!(seq === text && text.length === 1 && text >= " ")) this._undoInsertChain = false;
|
|
2553
2761
|
|
|
2554
|
-
//
|
|
2555
|
-
if (
|
|
2762
|
+
// Keep autocomplete active while cycling candidates with Tab/Shift-Tab or Up/Down.
|
|
2763
|
+
if (!["tab", "backtab", "up", "down"].includes(seq) && buf?.acHas) buf.clearAutocomplete();
|
|
2556
2764
|
|
|
2557
2765
|
switch (seq) {
|
|
2558
2766
|
case "escape": {
|
|
@@ -2575,40 +2783,9 @@ class App {
|
|
|
2575
2783
|
};
|
|
2576
2784
|
buf.cursor = { ...this.pane.selection.end };
|
|
2577
2785
|
break;
|
|
2578
|
-
case "ctrl-c":
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
const text = getSelectionText(buf, sel);
|
|
2582
|
-
this.clipboard.write(text);
|
|
2583
|
-
this.message = `Copied to ${this.clipboard.methodName()} clipboard`;
|
|
2584
|
-
} else {
|
|
2585
|
-
this.clipboard.write(buf.currentLineText() + "\n");
|
|
2586
|
-
this.message = `Copied line to ${this.clipboard.methodName()} clipboard`;
|
|
2587
|
-
}
|
|
2588
|
-
break;
|
|
2589
|
-
}
|
|
2590
|
-
case "ctrl-x": { //cut
|
|
2591
|
-
buf.pushUndo();
|
|
2592
|
-
if (this.pane?.selection) {
|
|
2593
|
-
const text = deleteSelection(buf, this.pane);
|
|
2594
|
-
this.clipboard.write(text);
|
|
2595
|
-
this.message = `Cut to ${this.clipboard.methodName()} clipboard`;
|
|
2596
|
-
} else {
|
|
2597
|
-
this.clipboard.write(buf.cutLine() + "\n");
|
|
2598
|
-
this.message = `Cut line to ${this.clipboard.methodName()} clipboard`;
|
|
2599
|
-
}
|
|
2600
|
-
break;
|
|
2601
|
-
}
|
|
2602
|
-
case "ctrl-v": { //paste
|
|
2603
|
-
const pasted = this.clipboard.read();
|
|
2604
|
-
if (pasted) {
|
|
2605
|
-
buf.pushUndo();
|
|
2606
|
-
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
2607
|
-
buf.insert(pasted);
|
|
2608
|
-
this.message = pasteStatusMessage(this.clipboard.methodName(), pasted);
|
|
2609
|
-
}
|
|
2610
|
-
break;
|
|
2611
|
-
}
|
|
2786
|
+
case "ctrl-c": await this.handleCommand("copy"); break; //copy
|
|
2787
|
+
case "ctrl-x": await this.handleCommand("cut"); break; //cut
|
|
2788
|
+
case "ctrl-v": await this.handleCommand("paste"); break; //paste
|
|
2612
2789
|
case "ctrl-z": //undo
|
|
2613
2790
|
if (buf.undo()) this.pane.selection = null;
|
|
2614
2791
|
else this.message = "Nothing to undo";
|
|
@@ -2722,17 +2899,7 @@ class App {
|
|
|
2722
2899
|
}
|
|
2723
2900
|
break;
|
|
2724
2901
|
}
|
|
2725
|
-
case "ctrl-k": //cutLine
|
|
2726
|
-
buf.pushUndo();
|
|
2727
|
-
if (this.pane?.selection) {
|
|
2728
|
-
const text = deleteSelection(buf, this.pane);
|
|
2729
|
-
this.clipboard.write(text);
|
|
2730
|
-
this.message = `Cut to ${this.clipboard.methodName()} clipboard`;
|
|
2731
|
-
} else {
|
|
2732
|
-
this.clipboard.write(buf.cutLine() + "\n");
|
|
2733
|
-
this.message = `Cut line to ${this.clipboard.methodName()} clipboard`;
|
|
2734
|
-
}
|
|
2735
|
-
break;
|
|
2902
|
+
case "ctrl-k": await this.handleCommand("cutline"); break; //cutLine
|
|
2736
2903
|
case "ctrl-o": //open
|
|
2737
2904
|
this.openCommandMode("open ");
|
|
2738
2905
|
break;
|
|
@@ -2799,10 +2966,18 @@ class App {
|
|
|
2799
2966
|
buf.moveRight();
|
|
2800
2967
|
break;
|
|
2801
2968
|
case "up":
|
|
2969
|
+
if (buf.acHas) {
|
|
2970
|
+
buf.cycleAutocomplete(false);
|
|
2971
|
+
break;
|
|
2972
|
+
}
|
|
2802
2973
|
this.pane.selection = null;
|
|
2803
2974
|
this._moveUpVisual(buf, this.pane);
|
|
2804
2975
|
break;
|
|
2805
2976
|
case "down":
|
|
2977
|
+
if (buf.acHas) {
|
|
2978
|
+
buf.cycleAutocomplete(true);
|
|
2979
|
+
break;
|
|
2980
|
+
}
|
|
2806
2981
|
this.pane.selection = null;
|
|
2807
2982
|
this._moveDownVisual(buf, this.pane);
|
|
2808
2983
|
break;
|
|
@@ -2832,11 +3007,11 @@ class App {
|
|
|
2832
3007
|
break;
|
|
2833
3008
|
case "shift-pageup":
|
|
2834
3009
|
buf._lastVisX = null;
|
|
2835
|
-
|
|
3010
|
+
this.cursorPage(this.pane, -1, { select: true });
|
|
2836
3011
|
break;
|
|
2837
3012
|
case "shift-pagedown":
|
|
2838
3013
|
buf._lastVisX = null;
|
|
2839
|
-
|
|
3014
|
+
this.cursorPage(this.pane, 1, { select: true });
|
|
2840
3015
|
break;
|
|
2841
3016
|
case "home":
|
|
2842
3017
|
buf._lastVisX = null;
|
|
@@ -2904,11 +3079,11 @@ class App {
|
|
|
2904
3079
|
break;
|
|
2905
3080
|
case "pageup":
|
|
2906
3081
|
buf._lastVisX = null;
|
|
2907
|
-
await runAction("
|
|
3082
|
+
await runAction("CursorPageUp", this);
|
|
2908
3083
|
break;
|
|
2909
3084
|
case "pagedown":
|
|
2910
3085
|
buf._lastVisX = null;
|
|
2911
|
-
await runAction("
|
|
3086
|
+
await runAction("CursorPageDown", this);
|
|
2912
3087
|
break;
|
|
2913
3088
|
case "tab":
|
|
2914
3089
|
if (buf.acHas) buf.cycleAutocomplete(true);
|
|
@@ -2980,14 +3155,14 @@ class App {
|
|
|
2980
3155
|
}
|
|
2981
3156
|
if (ch < " " && ch !== "\t") continue;
|
|
2982
3157
|
buf.insertChar(ch);
|
|
2983
|
-
await this.context.plugins?.run("onRune", makePaneAdapter(buf), ch);
|
|
2984
|
-
await this.context.jsPlugins?.run("onRune", makePaneAdapter(buf), ch);
|
|
3158
|
+
await this.context.plugins?.run("onRune", makePaneAdapter(buf, this), ch);
|
|
3159
|
+
await this.context.jsPlugins?.run("onRune", makePaneAdapter(buf, this), ch);
|
|
2985
3160
|
}
|
|
2986
3161
|
}
|
|
2987
3162
|
|
|
2988
3163
|
async runPluginBool(fn) {
|
|
2989
|
-
const luaOk = await this.context.plugins?.runBool(fn, makePaneAdapter(this.buffer)) ?? true;
|
|
2990
|
-
const jsOk = await this.context.jsPlugins?.runBool(fn, makePaneAdapter(this.buffer)) ?? true;
|
|
3164
|
+
const luaOk = await this.context.plugins?.runBool(fn, makePaneAdapter(this.buffer, this)) ?? true;
|
|
3165
|
+
const jsOk = await this.context.jsPlugins?.runBool(fn, makePaneAdapter(this.buffer, this)) ?? true;
|
|
2991
3166
|
return luaOk && jsOk;
|
|
2992
3167
|
}
|
|
2993
3168
|
|
|
@@ -3002,6 +3177,10 @@ class App {
|
|
|
3002
3177
|
this.render();
|
|
3003
3178
|
return;
|
|
3004
3179
|
}
|
|
3180
|
+
if (await this.handleMessageRowMouse(event)) {
|
|
3181
|
+
this.render();
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3005
3184
|
if (await this.handleStatusBarMouse(event)) {
|
|
3006
3185
|
this.render();
|
|
3007
3186
|
return;
|
|
@@ -3052,7 +3231,7 @@ class App {
|
|
|
3052
3231
|
}
|
|
3053
3232
|
return;
|
|
3054
3233
|
}
|
|
3055
|
-
if (!["down", "up", "drag"].includes(event.action) || !["left", "none"].includes(event.button)) return;
|
|
3234
|
+
if (!["down", "up", "drag"].includes(event.action) || !["left", "none", "middle"].includes(event.button)) return;
|
|
3056
3235
|
buf.allowCursorOffscreen = false;
|
|
3057
3236
|
const gutterW = _swGutterW;
|
|
3058
3237
|
const localY = event.y - clicked.y;
|
|
@@ -3073,6 +3252,18 @@ class App {
|
|
|
3073
3252
|
}
|
|
3074
3253
|
buf.cursor.y = y;
|
|
3075
3254
|
buf.cursor.x = x;
|
|
3255
|
+
if (event.button === "middle") {
|
|
3256
|
+
if (event.action === "down") {
|
|
3257
|
+
const pasted = this.clipboard.read("primary");
|
|
3258
|
+
if (pasted) {
|
|
3259
|
+
buf.pushUndo();
|
|
3260
|
+
this.pane.selection = null;
|
|
3261
|
+
buf.insert(pasted);
|
|
3262
|
+
this.message = pasteStatusMessage("primary", pasted);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3076
3267
|
if (event.action === "down") {
|
|
3077
3268
|
if (inGutter) {
|
|
3078
3269
|
// Message column (first msgW cols): show message text in infobar, no selection.
|
|
@@ -3154,6 +3345,15 @@ class App {
|
|
|
3154
3345
|
buf.ensureCursor();
|
|
3155
3346
|
}
|
|
3156
3347
|
|
|
3348
|
+
_syncPrimarySelection() {
|
|
3349
|
+
const sel = this.pane?.selection;
|
|
3350
|
+
if (!sel || sameLoc(sel.start, sel.end)) return;
|
|
3351
|
+
const buf = this.buffer;
|
|
3352
|
+
if (!buf) return;
|
|
3353
|
+
const text = getSelectionText(buf, sel);
|
|
3354
|
+
if (text) this.clipboard.write(text, "primary");
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3157
3357
|
handleSuggestionMouse(event) {
|
|
3158
3358
|
if (this._suggestionsRow == null || event.y !== this._suggestionsRow) return false;
|
|
3159
3359
|
if (event.action !== "down" || event.button !== "left") return false;
|
|
@@ -3173,6 +3373,19 @@ class App {
|
|
|
3173
3373
|
return true;
|
|
3174
3374
|
}
|
|
3175
3375
|
|
|
3376
|
+
async handleMessageRowMouse(event) {
|
|
3377
|
+
if (this._messageRowY == null || event.y !== this._messageRowY) return false;
|
|
3378
|
+
if (event.action !== "down" || event.button !== "left") return false;
|
|
3379
|
+
const zone = this._messageRowClickZone;
|
|
3380
|
+
if (!zone || !this._messageClickAction) return false;
|
|
3381
|
+
if (event.x < zone.start || event.x >= zone.end) return false;
|
|
3382
|
+
const result = this._messageClickAction();
|
|
3383
|
+
this._messageClickAction = null;
|
|
3384
|
+
this._messageRowClickZone = null;
|
|
3385
|
+
if (typeof result === "string") this.message = result;
|
|
3386
|
+
return true;
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3176
3389
|
async handleStatusBarMouse(event) {
|
|
3177
3390
|
if (this._statusBarRow == null || event.y !== this._statusBarRow) return false;
|
|
3178
3391
|
if (event.action !== "down" || event.button !== "left") return false;
|
|
@@ -3230,7 +3443,11 @@ class App {
|
|
|
3230
3443
|
break;
|
|
3231
3444
|
}
|
|
3232
3445
|
case "fmt":
|
|
3233
|
-
if (buf) {
|
|
3446
|
+
if (buf) {
|
|
3447
|
+
buf.fileformat = buf.fileformat === "dos" ? "unix" : "dos";
|
|
3448
|
+
buf.Settings.fileformat = buf.fileformat;
|
|
3449
|
+
buf.modified = true;
|
|
3450
|
+
}
|
|
3234
3451
|
break;
|
|
3235
3452
|
case "enc":
|
|
3236
3453
|
if (buf) {
|
|
@@ -3290,9 +3507,9 @@ class App {
|
|
|
3290
3507
|
if (index < 0 || index >= this.tabs.length || index === this.activeTabIdx) return false;
|
|
3291
3508
|
this.activeTabIdx = index;
|
|
3292
3509
|
this.message = "";
|
|
3293
|
-
if (this.context.plugins && this.buffer) this.context.plugins.curPaneAdapter = makePaneAdapter(this.buffer);
|
|
3294
|
-
this.context.plugins?.run("onSetActive", makePaneAdapter(this.buffer));
|
|
3295
|
-
if (this.buffer) this.context.jsPlugins?.run("onSetActive", makePaneAdapter(this.buffer));
|
|
3510
|
+
if (this.context.plugins && this.buffer) this.context.plugins.curPaneAdapter = makePaneAdapter(this.buffer, this);
|
|
3511
|
+
this.context.plugins?.run("onSetActive", makePaneAdapter(this.buffer, this));
|
|
3512
|
+
if (this.buffer) this.context.jsPlugins?.run("onSetActive", makePaneAdapter(this.buffer, this));
|
|
3296
3513
|
return true;
|
|
3297
3514
|
}
|
|
3298
3515
|
|
|
@@ -3543,14 +3760,14 @@ class App {
|
|
|
3543
3760
|
this.openPrompt("Save as: ", async (value) => {
|
|
3544
3761
|
if (value) {
|
|
3545
3762
|
await this.buffer.save(resolve(expandHome(value)));
|
|
3546
|
-
await this.context.plugins?.run("onSave", makePaneAdapter(this.buffer));
|
|
3547
|
-
await this.context.jsPlugins?.run("onSave", makePaneAdapter(this.buffer));
|
|
3763
|
+
await this.context.plugins?.run("onSave", makePaneAdapter(this.buffer, this));
|
|
3764
|
+
await this.context.jsPlugins?.run("onSave", makePaneAdapter(this.buffer, this));
|
|
3548
3765
|
}
|
|
3549
3766
|
}, { completer: fileComplete, initial });
|
|
3550
3767
|
} else {
|
|
3551
3768
|
await this.buffer.save();
|
|
3552
|
-
await this.context.plugins?.run("onSave", makePaneAdapter(this.buffer));
|
|
3553
|
-
await this.context.jsPlugins?.run("onSave", makePaneAdapter(this.buffer));
|
|
3769
|
+
await this.context.plugins?.run("onSave", makePaneAdapter(this.buffer, this));
|
|
3770
|
+
await this.context.jsPlugins?.run("onSave", makePaneAdapter(this.buffer, this));
|
|
3554
3771
|
await this._saveCursorForBuf(this.buffer);
|
|
3555
3772
|
}
|
|
3556
3773
|
} catch (error) {
|
|
@@ -3707,10 +3924,10 @@ class App {
|
|
|
3707
3924
|
this.tabs.splice(this.activeTabIdx, 1);
|
|
3708
3925
|
this.activeTabIdx = Math.min(this.activeTabIdx, this.tabs.length - 1);
|
|
3709
3926
|
this.message = "";
|
|
3710
|
-
if (this.context.plugins && this.buffer) this.context.plugins.curPaneAdapter = makePaneAdapter(this.buffer);
|
|
3711
|
-
await this.context.plugins?.run("onSetActive", makePaneAdapter(this.buffer));
|
|
3927
|
+
if (this.context.plugins && this.buffer) this.context.plugins.curPaneAdapter = makePaneAdapter(this.buffer, this);
|
|
3928
|
+
await this.context.plugins?.run("onSetActive", makePaneAdapter(this.buffer, this));
|
|
3712
3929
|
await this.context.plugins?.run("onBufferClose", closing);
|
|
3713
|
-
if (this.buffer) this.context.jsPlugins?.run("onSetActive", makePaneAdapter(this.buffer));
|
|
3930
|
+
if (this.buffer) this.context.jsPlugins?.run("onSetActive", makePaneAdapter(this.buffer, this));
|
|
3714
3931
|
await this.context.jsPlugins?.run("onBufferClose", closing);
|
|
3715
3932
|
this.render();
|
|
3716
3933
|
}
|
|
@@ -3883,6 +4100,9 @@ class App {
|
|
|
3883
4100
|
this.message = `colorscheme: ${err.message}`;
|
|
3884
4101
|
}
|
|
3885
4102
|
}
|
|
4103
|
+
if (opt === "clipboard") {
|
|
4104
|
+
await this.reinitializeClipboard(buf.Settings[opt]);
|
|
4105
|
+
}
|
|
3886
4106
|
}
|
|
3887
4107
|
break;
|
|
3888
4108
|
}
|
|
@@ -3929,8 +4149,8 @@ class App {
|
|
|
3929
4149
|
if (answer === "y") {
|
|
3930
4150
|
try {
|
|
3931
4151
|
await buf.save(target);
|
|
3932
|
-
await this.context.plugins?.run("onSave", makePaneAdapter(buf));
|
|
3933
|
-
await this.context.jsPlugins?.run("onSave", makePaneAdapter(buf));
|
|
4152
|
+
await this.context.plugins?.run("onSave", makePaneAdapter(buf, this));
|
|
4153
|
+
await this.context.jsPlugins?.run("onSave", makePaneAdapter(buf, this));
|
|
3934
4154
|
} catch (err) {
|
|
3935
4155
|
this.message = err.message;
|
|
3936
4156
|
}
|
|
@@ -3940,8 +4160,8 @@ class App {
|
|
|
3940
4160
|
} else if (saveArgs.length > 0) {
|
|
3941
4161
|
try {
|
|
3942
4162
|
await buf.save(resolve(expandHome(saveArgs[0])));
|
|
3943
|
-
await this.context.plugins?.run("onSave", makePaneAdapter(buf));
|
|
3944
|
-
await this.context.jsPlugins?.run("onSave", makePaneAdapter(buf));
|
|
4163
|
+
await this.context.plugins?.run("onSave", makePaneAdapter(buf, this));
|
|
4164
|
+
await this.context.jsPlugins?.run("onSave", makePaneAdapter(buf, this));
|
|
3945
4165
|
}
|
|
3946
4166
|
catch (err) { this.message = err.message; }
|
|
3947
4167
|
} else {
|
|
@@ -3998,10 +4218,9 @@ class App {
|
|
|
3998
4218
|
this.toggleComment();
|
|
3999
4219
|
break;
|
|
4000
4220
|
case "goto": {
|
|
4001
|
-
if (cmdArgs.length === 0) { this.message = "Usage: goto <line[:col]>"; break; }
|
|
4221
|
+
if (cmdArgs.length === 0) { this.message = "Usage: goto <line[.subrow][:col]>"; break; }
|
|
4002
4222
|
try {
|
|
4003
|
-
|
|
4004
|
-
buf.gotoLoc(line, col);
|
|
4223
|
+
this.gotoLocation(buf, parseLineCol(cmdArgs[0]), this.pane);
|
|
4005
4224
|
this.pane.selection = null;
|
|
4006
4225
|
} catch (error) {
|
|
4007
4226
|
this.message = String(error.message || error);
|
|
@@ -4195,7 +4414,7 @@ class App {
|
|
|
4195
4414
|
await this.context.plugins?.run("onBufferOpen", buf);
|
|
4196
4415
|
await this.context.jsPlugins?.run("onBufferOpen", buf);
|
|
4197
4416
|
}
|
|
4198
|
-
if (cmd !== "togglelocal" && cfg && opt in cfg.globalSettings) {
|
|
4417
|
+
if (cmd !== "togglelocal" && cfg && opt in cfg.globalSettings && !LOCAL_SETTINGS.has(opt)) {
|
|
4199
4418
|
try { cfg.setGlobalOptionNative(opt, newVal, { modified: true }); await cfg.saveSettings(); } catch {}
|
|
4200
4419
|
}
|
|
4201
4420
|
break;
|
|
@@ -4209,12 +4428,13 @@ class App {
|
|
|
4209
4428
|
const defVal = opt in defaults ? defaults[opt] : true;
|
|
4210
4429
|
try { buf.SetOption(opt, String(defVal)); } catch (err) { this.message = String(err.message || err); break; }
|
|
4211
4430
|
this.message = `${opt} = ${defVal}`;
|
|
4212
|
-
if (cfgR && opt in cfgR.globalSettings) {
|
|
4431
|
+
if (cfgR && opt in cfgR.globalSettings && !LOCAL_SETTINGS.has(opt)) {
|
|
4213
4432
|
try { cfgR.setGlobalOptionNative(opt, defVal, { modified: true }); await cfgR.saveSettings(); } catch {}
|
|
4214
4433
|
}
|
|
4215
4434
|
if (opt === "colorscheme" && this.context?.runtime) {
|
|
4216
4435
|
try { this.context.colorscheme = await new Colorscheme(this.context.runtime).load(String(defVal)); } catch {}
|
|
4217
4436
|
}
|
|
4437
|
+
if (opt === "clipboard") await this.reinitializeClipboard(defVal);
|
|
4218
4438
|
break;
|
|
4219
4439
|
}
|
|
4220
4440
|
case "jump": {
|
|
@@ -4363,11 +4583,66 @@ class App {
|
|
|
4363
4583
|
await this.runAlert(content);
|
|
4364
4584
|
break;
|
|
4365
4585
|
}
|
|
4586
|
+
case "copy": {
|
|
4587
|
+
this._freshClip = false;
|
|
4588
|
+
const sel = this.pane?.selection;
|
|
4589
|
+
const copyText = sel ? getSelectionText(buf, sel) : (buf.currentLineText() + "\n");
|
|
4590
|
+
this.clipboard.write(copyText);
|
|
4591
|
+
this.message = clipboardCopyMsg(this.clipboard, copyText, sel ? "selection" : "line");
|
|
4592
|
+
this._messageClickAction = clipboardAltAction(this.clipboard, copyText);
|
|
4593
|
+
break;
|
|
4594
|
+
}
|
|
4595
|
+
case "cut": {
|
|
4596
|
+
this._freshClip = false;
|
|
4597
|
+
buf.pushUndo();
|
|
4598
|
+
const cutText = this.pane?.selection
|
|
4599
|
+
? deleteSelection(buf, this.pane)
|
|
4600
|
+
: (buf.cutLine() + "\n");
|
|
4601
|
+
const cutKind = this.pane?.selection ? "selection" : "line";
|
|
4602
|
+
this.clipboard.write(cutText);
|
|
4603
|
+
this.message = clipboardCopyMsg(this.clipboard, cutText, cutKind, "Cut");
|
|
4604
|
+
this._messageClickAction = clipboardAltAction(this.clipboard, cutText);
|
|
4605
|
+
break;
|
|
4606
|
+
}
|
|
4607
|
+
case "cutline": {
|
|
4608
|
+
buf.pushUndo();
|
|
4609
|
+
if (this.pane?.selection) {
|
|
4610
|
+
this._freshClip = false;
|
|
4611
|
+
const text = deleteSelection(buf, this.pane);
|
|
4612
|
+
this.clipboard.write(text);
|
|
4613
|
+
this.message = clipboardCopyMsg(this.clipboard, text, "selection", "Cut");
|
|
4614
|
+
this._messageClickAction = clipboardAltAction(this.clipboard, text);
|
|
4615
|
+
} else {
|
|
4616
|
+
const prev = this._freshClip ? (this.clipboard.read() ?? "") : "";
|
|
4617
|
+
const line = buf.cutLine() + "\n";
|
|
4618
|
+
this.clipboard.write(prev + line);
|
|
4619
|
+
this._freshClip = true;
|
|
4620
|
+
const total = (prev + line).split("\n").length - 1;
|
|
4621
|
+
const label = total > 1 ? `${total} lines` : "line";
|
|
4622
|
+
this.message = clipboardCopyMsg(this.clipboard, prev + line, label, "Cut");
|
|
4623
|
+
this._messageClickAction = clipboardAltAction(this.clipboard, prev + line);
|
|
4624
|
+
}
|
|
4625
|
+
break;
|
|
4626
|
+
}
|
|
4627
|
+
case "paste": {
|
|
4628
|
+
this._freshClip = false;
|
|
4629
|
+
const pasted = this.clipboard.read();
|
|
4630
|
+
if (pasted) {
|
|
4631
|
+
buf.pushUndo();
|
|
4632
|
+
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
4633
|
+
buf.insert(pasted);
|
|
4634
|
+
this.message = pasteStatusMessage(this.clipboard.readMethodName(), pasted);
|
|
4635
|
+
}
|
|
4636
|
+
break;
|
|
4637
|
+
}
|
|
4638
|
+
case "pasteprimary":
|
|
4639
|
+
await runAction("PastePrimary", this);
|
|
4640
|
+
break;
|
|
4366
4641
|
default: {
|
|
4367
4642
|
const pluginCmd = this.context.plugins?.commands?.get(cmd);
|
|
4368
4643
|
if (pluginCmd) {
|
|
4369
4644
|
try {
|
|
4370
|
-
await pluginCmd(makePaneAdapter(this.buffer), cmdArgs);
|
|
4645
|
+
await pluginCmd(makePaneAdapter(this.buffer, this), cmdArgs);
|
|
4371
4646
|
} catch (e) {
|
|
4372
4647
|
this.message = String(e.message ?? e);
|
|
4373
4648
|
}
|
|
@@ -4692,6 +4967,7 @@ const COMMAND_NAMES = [
|
|
|
4692
4967
|
"cd", "pwd", "tab", "run", "vsplit", "hsplit", "term", "tts", "ttsspeed", "ttspitch", "ttslang", "reopen", "theme", "toggle", "tog",
|
|
4693
4968
|
"togglelocal", "reset", "jump", "tabmove", "tabswitch", "textfilter", "bind", "unbind", "reload", "lintlog", "act", "action", "raw",
|
|
4694
4969
|
"help", "plugin", "showkey", "memusage", "retab", "eval",
|
|
4970
|
+
"copy", "cut", "cutline", "paste", "pasteprimary",
|
|
4695
4971
|
];
|
|
4696
4972
|
|
|
4697
4973
|
const SUPPORTED_ENCODING_LABELS = [
|
|
@@ -4886,10 +5162,40 @@ function isEmptyUntitledBuffer(buffer) {
|
|
|
4886
5162
|
return !buffer.path && !buffer.modified && buffer.lines.length === 1 && buffer.lines[0] === "";
|
|
4887
5163
|
}
|
|
4888
5164
|
|
|
4889
|
-
function
|
|
5165
|
+
function formatAbsoluteCursorLocation(buffer) {
|
|
5166
|
+
if (!buffer) return "+1:1";
|
|
5167
|
+
const y = clamp(buffer.cursor?.y ?? 0, 0, Math.max(0, (buffer.lines?.length ?? 1) - 1));
|
|
5168
|
+
const line = buffer.lines?.[y] ?? "";
|
|
5169
|
+
const x = normalizeCharBoundary(line, buffer.cursor?.x ?? 0);
|
|
5170
|
+
return `+${y + 1}:${x + 1}`;
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
function formatCursorLocation(buffer, pane = null) {
|
|
5174
|
+
if (!buffer) return "+1.0:1";
|
|
5175
|
+
const y = clamp(buffer.cursor?.y ?? 0, 0, Math.max(0, (buffer.lines?.length ?? 1) - 1));
|
|
5176
|
+
const line = buffer.lines?.[y] ?? "";
|
|
5177
|
+
const x = normalizeCharBoundary(line, buffer.cursor?.x ?? 0);
|
|
5178
|
+
let subRow = 0;
|
|
5179
|
+
let col = x + 1;
|
|
5180
|
+
if (pane && (buffer.Settings?.softwrap ?? false)) {
|
|
5181
|
+
const gutterW = editorGutterWidth(buffer);
|
|
5182
|
+
const bufW = Math.max(1, (pane.w ?? process.stdout.columns ?? 80) - gutterW);
|
|
5183
|
+
const wordwrap = buffer.Settings?.wordwrap ?? false;
|
|
5184
|
+
const tabsize = buffer.Settings?.tabsize ?? DEFAULT_SETTINGS.tabsize;
|
|
5185
|
+
const breaks = softwrapBreaks(line, bufW, wordwrap, tabsize);
|
|
5186
|
+
subRow = softwrapRowOfCharIdx(breaks, x);
|
|
5187
|
+
const segStart = breaks[subRow] ?? 0;
|
|
5188
|
+
col = displayWidth(line.slice(segStart, x)) + 1;
|
|
5189
|
+
}
|
|
5190
|
+
return `+${y + 1}.${subRow}:${col}`;
|
|
5191
|
+
}
|
|
5192
|
+
|
|
5193
|
+
function makePaneAdapter(buffer, app = null) {
|
|
4890
5194
|
const pane = {
|
|
4891
5195
|
Buf: makeBufferAdapter(buffer),
|
|
4892
5196
|
Cursor: makeCursorAdapter(buffer),
|
|
5197
|
+
CursorLocation: () => formatCursorLocation(buffer, app?.paneForBuffer?.(buffer) ?? app?.pane ?? null),
|
|
5198
|
+
AbsoluteCursorLocation: () => formatAbsoluteCursorLocation(buffer),
|
|
4893
5199
|
Save: () => buffer.save(),
|
|
4894
5200
|
Backspace: () => buffer.backspace(),
|
|
4895
5201
|
Delete: () => buffer.deleteForward(),
|
|
@@ -5198,19 +5504,27 @@ function detectTtsCmd() {
|
|
|
5198
5504
|
return null;
|
|
5199
5505
|
}
|
|
5200
5506
|
|
|
5201
|
-
|
|
5507
|
+
function commandHasStartCursor(command = {}) {
|
|
5508
|
+
return Boolean(command.startCursor && command.startCursor.line >= 1);
|
|
5509
|
+
}
|
|
5510
|
+
|
|
5511
|
+
function commandHasStartupJump(command = {}) {
|
|
5512
|
+
return commandHasStartCursor(command) || Boolean(command.searchRegex);
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
async function loadBufferForPath(pathOrUrl, context, command = {}) {
|
|
5202
5516
|
if (isHttpUrl(pathOrUrl)) {
|
|
5203
5517
|
let encoding = context.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
|
|
5204
5518
|
const decoded = await fetchTextWithEncoding(pathOrUrl, encoding);
|
|
5205
5519
|
const text = decoded.text;
|
|
5206
5520
|
encoding = decoded.encoding;
|
|
5207
5521
|
const urlPath = pathOrUrl.replace(/[?#].*$/, "");
|
|
5208
|
-
const buffer = new BufferModel({ path: pathOrUrl, text, command
|
|
5522
|
+
const buffer = new BufferModel({ path: pathOrUrl, text, command, encoding });
|
|
5209
5523
|
attachSyntax(buffer, context, urlPath, text);
|
|
5210
5524
|
return buffer;
|
|
5211
5525
|
}
|
|
5212
|
-
const buffer = await BufferModel.fromFile(pathOrUrl,
|
|
5213
|
-
if (DEFAULT_SETTINGS.savecursor && context?.cursorStates?.[pathOrUrl]) {
|
|
5526
|
+
const buffer = await BufferModel.fromFile(pathOrUrl, command, context);
|
|
5527
|
+
if (DEFAULT_SETTINGS.savecursor && !commandHasStartupJump(command) && context?.cursorStates?.[pathOrUrl]) {
|
|
5214
5528
|
const saved = context.cursorStates[pathOrUrl];
|
|
5215
5529
|
const y = clamp(saved.y ?? 0, 0, buffer.lines.length - 1);
|
|
5216
5530
|
const x = clamp(saved.x ?? 0, 0, buffer.lines[y]?.length ?? 0);
|
|
@@ -5470,6 +5784,15 @@ function lineNumberText(buf, lineNo, row, gutterW) {
|
|
|
5470
5784
|
return String(lineNo + 1).padStart(Math.max(0, gutterW - 1)) + " ";
|
|
5471
5785
|
}
|
|
5472
5786
|
|
|
5787
|
+
function visualLineNumberText(subRow, gutterW) {
|
|
5788
|
+
const text = `.${subRow}`;
|
|
5789
|
+
const numberW = Math.max(0, gutterW - 1);
|
|
5790
|
+
const padded = text.length >= numberW
|
|
5791
|
+
? text.slice(text.length - numberW)
|
|
5792
|
+
: text.padStart(numberW);
|
|
5793
|
+
return padded + " ";
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5473
5796
|
const BRACE_PAIRS = { "(": ")", "[": "]", "{": "}" };
|
|
5474
5797
|
const BRACE_REVERSE = { ")": "(", "]": "[", "}": "{" };
|
|
5475
5798
|
|
|
@@ -5732,7 +6055,7 @@ async function loadBuffers(files, command) {
|
|
|
5732
6055
|
if (files.length > 0) {
|
|
5733
6056
|
for (const file of files) {
|
|
5734
6057
|
try {
|
|
5735
|
-
buffers.push(await loadBufferForPath(file, loadBuffers.context ?? {}));
|
|
6058
|
+
buffers.push(await loadBufferForPath(file, loadBuffers.context ?? {}, command));
|
|
5736
6059
|
} catch (error) {
|
|
5737
6060
|
console.error(error.message || error);
|
|
5738
6061
|
}
|
|
@@ -5750,6 +6073,11 @@ async function loadBuffers(files, command) {
|
|
|
5750
6073
|
return buffers.length ? buffers : [new BufferModel({ command })];
|
|
5751
6074
|
}
|
|
5752
6075
|
|
|
6076
|
+
async function printReadmeDocs() {
|
|
6077
|
+
const readme = await Bun.file(join(REPO_ROOT, "README.md")).text();
|
|
6078
|
+
process.stdout.write(Bun.markdown.ansi(readme, { hyperlinks: true }));
|
|
6079
|
+
}
|
|
6080
|
+
|
|
5753
6081
|
async function main() {
|
|
5754
6082
|
const { flags, files: rawFiles } = parseArgs(process.argv.slice(2));
|
|
5755
6083
|
if (flags.help) {
|
|
@@ -5757,7 +6085,6 @@ async function main() {
|
|
|
5757
6085
|
return;
|
|
5758
6086
|
}
|
|
5759
6087
|
if (flags.version) {
|
|
5760
|
-
const clipboard = new ClipboardManager();
|
|
5761
6088
|
const ttsCmd = detectTtsCmd();
|
|
5762
6089
|
console.log(pkg.name+":",pkg.description)
|
|
5763
6090
|
console.log(" Rewritten by: Dr. John (醫者小智)")
|
|
@@ -5766,9 +6093,24 @@ async function main() {
|
|
|
5766
6093
|
console.log("Runtime:", `Bun ${Bun.version}`);
|
|
5767
6094
|
console.log("Platform:", platformId());
|
|
5768
6095
|
console.log("Http client:",detectHttpBackend());
|
|
5769
|
-
console.log("Clipboard:", clipboard.methodName());
|
|
5770
6096
|
console.log("TTS:", ttsCmd ? ttsCmd.cmd[0] : "not found");
|
|
5771
6097
|
console.log({SUPPORTED_ENCODING_LABELS})
|
|
6098
|
+
const clipboard = new ClipboardManager();
|
|
6099
|
+
let osc52Available = false;
|
|
6100
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
6101
|
+
process.stdin.setRawMode?.(true);
|
|
6102
|
+
process.stdin.resume();
|
|
6103
|
+
osc52Available = await probeOSC52(process.stdin, process.stdout, 150);
|
|
6104
|
+
process.stdin.setRawMode?.(false);
|
|
6105
|
+
process.stdin.pause();
|
|
6106
|
+
}
|
|
6107
|
+
const externalName = clipboard.methodName();
|
|
6108
|
+
const backends = osc52Available ? `${externalName}, OSC 52` : externalName;
|
|
6109
|
+
console.log("Clipboard:", backends);
|
|
6110
|
+
return;
|
|
6111
|
+
}
|
|
6112
|
+
if (flags.docs) {
|
|
6113
|
+
await printReadmeDocs();
|
|
5772
6114
|
return;
|
|
5773
6115
|
}
|
|
5774
6116
|
if (flags.options) {
|
|
@@ -5789,7 +6131,7 @@ async function main() {
|
|
|
5789
6131
|
const syntaxDefinitions = await loadSyntaxDefinitions(runtime);
|
|
5790
6132
|
|
|
5791
6133
|
if (flags.cat) {
|
|
5792
|
-
await catFiles(rawFiles, colorscheme, syntaxDefinitions);
|
|
6134
|
+
await catFiles(rawFiles, colorscheme, syntaxDefinitions, config.getGlobalOption("encoding"));
|
|
5793
6135
|
return;
|
|
5794
6136
|
}
|
|
5795
6137
|
|
|
@@ -5827,8 +6169,8 @@ async function main() {
|
|
|
5827
6169
|
}
|
|
5828
6170
|
|
|
5829
6171
|
if (flags.clean) {
|
|
5830
|
-
|
|
5831
|
-
|
|
6172
|
+
await cleanConfig(config, plugins);
|
|
6173
|
+
return;
|
|
5832
6174
|
}
|
|
5833
6175
|
|
|
5834
6176
|
const pluginErr = await plugins.loadAll();
|
|
@@ -5870,7 +6212,7 @@ async function main() {
|
|
|
5870
6212
|
}
|
|
5871
6213
|
const app = new App(buffers, context);
|
|
5872
6214
|
jsPlugins.setApp(app);
|
|
5873
|
-
if (plugins && !pluginErr && app.buffer) plugins.curPaneAdapter = makePaneAdapter(app.buffer);
|
|
6215
|
+
if (plugins && !pluginErr && app.buffer) plugins.curPaneAdapter = makePaneAdapter(app.buffer, app);
|
|
5874
6216
|
// Dispatch all JS plugin lifecycle hooks after setApp so TermMessage,
|
|
5875
6217
|
// CurPane, cmd/action proxies, and buffer APIs all work correctly.
|
|
5876
6218
|
await jsPlugins.run("preinit");
|
|
@@ -6006,6 +6348,26 @@ function uncommentText(line, commentType) {
|
|
|
6006
6348
|
}
|
|
6007
6349
|
return indent + rest;
|
|
6008
6350
|
}
|
|
6351
|
+
function clipboardCopyMsg(clipboard, text, kind, verb = "Copied") {
|
|
6352
|
+
const method = clipboard.methodName();
|
|
6353
|
+
const alt = clipboard.altMethodName();
|
|
6354
|
+
const chars = Array.from(String(text).replace(/\n$/, "")).length;
|
|
6355
|
+
const label = typeof kind === "string" && (kind === "line" || kind.endsWith("lines"))
|
|
6356
|
+
? kind : `${chars} chars`;
|
|
6357
|
+
if (alt) return `[Click:Copy>${alt}] ${method}: ${label} ${verb.toLowerCase()}`;
|
|
6358
|
+
return `${verb} ${label} to ${method} clipboard`;
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6361
|
+
function clipboardAltAction(clipboard, text) {
|
|
6362
|
+
const alt = clipboard.altMethodName();
|
|
6363
|
+
if (!alt) return null;
|
|
6364
|
+
return () => {
|
|
6365
|
+
if (!clipboard.writeAlt(text)) return `${alt}: failed`;
|
|
6366
|
+
const chars = Array.from(String(text).replace(/\n$/, "")).length;
|
|
6367
|
+
return `${alt}: ${chars} chars copied`;
|
|
6368
|
+
};
|
|
6369
|
+
}
|
|
6370
|
+
|
|
6009
6371
|
function pasteStatusMessage(method, text) {
|
|
6010
6372
|
const value = String(text);
|
|
6011
6373
|
const lines = value.split("\n").length;
|
|
@@ -6187,14 +6549,16 @@ function segmentSelection(selection, lineNo, start, end) {
|
|
|
6187
6549
|
function parseLineCol(value) {
|
|
6188
6550
|
const input = String(value).trim();
|
|
6189
6551
|
if (!input) throw new Error("Not enough arguments");
|
|
6190
|
-
const
|
|
6191
|
-
if (
|
|
6192
|
-
|
|
6193
|
-
const
|
|
6194
|
-
const col =
|
|
6552
|
+
const match = input.match(/^(-?\d+)(?:\.(\d+))?(?::(-?\d+))?$/);
|
|
6553
|
+
if (!match) throw new Error("Invalid line number");
|
|
6554
|
+
const line = Number(match[1]);
|
|
6555
|
+
const subRow = match[2] == null ? 0 : Number(match[2]);
|
|
6556
|
+
const col = match[3] == null ? 1 : Number(match[3]);
|
|
6195
6557
|
if (!Number.isInteger(line)) throw new Error("Invalid line number");
|
|
6558
|
+
if (!Number.isInteger(subRow)) throw new Error("Invalid visual line number");
|
|
6559
|
+
if (subRow < 0) throw new Error("Invalid visual line number");
|
|
6196
6560
|
if (!Number.isInteger(col)) throw new Error("Invalid column number");
|
|
6197
|
-
return { line, col };
|
|
6561
|
+
return { line, subRow, col };
|
|
6198
6562
|
}
|
|
6199
6563
|
|
|
6200
6564
|
function parseOptionValue(value) {
|
|
@@ -6210,19 +6574,23 @@ function syncEditorSettings(config) {
|
|
|
6210
6574
|
}
|
|
6211
6575
|
}
|
|
6212
6576
|
|
|
6213
|
-
async function catFiles(files, colorscheme, syntaxDefinitions) {
|
|
6577
|
+
async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding) {
|
|
6214
6578
|
const targets = files.length > 0 ? files.map((f) => ({ path: f, stdin: false })) : [{ path: null, stdin: true }];
|
|
6215
6579
|
for (const { path: filePath, stdin } of targets) {
|
|
6216
6580
|
let content;
|
|
6217
6581
|
let effectivePath = filePath;
|
|
6218
6582
|
if (stdin) {
|
|
6219
|
-
|
|
6583
|
+
const chunks = [];
|
|
6584
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
6585
|
+
content = decodeTextBytesWithEncoding(Buffer.concat(chunks), encoding).text;
|
|
6220
6586
|
} else if (isHttpUrl(filePath)) {
|
|
6221
|
-
|
|
6587
|
+
const decoded = await fetchTextWithEncoding(filePath, encoding);
|
|
6588
|
+
content = decoded.text;
|
|
6222
6589
|
// Use the URL pathname for syntax/md detection (strip query/hash)
|
|
6223
6590
|
try { effectivePath = new URL(filePath).pathname; } catch { effectivePath = filePath; }
|
|
6224
6591
|
} else {
|
|
6225
|
-
|
|
6592
|
+
const decoded = await readTextFileWithEncoding(filePath, encoding);
|
|
6593
|
+
content = decoded.text;
|
|
6226
6594
|
}
|
|
6227
6595
|
if (effectivePath && /\.md$/i.test(effectivePath)) {
|
|
6228
6596
|
process.stdout.write(
|