bunmicro 0.9.21 → 0.9.23
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 +12 -0
- package/package.json +1 -1
- package/src/highlight/parser.js +15 -10
- package/src/index.js +75 -24
- package/src/runtime/registry.js +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.23] - 2026-06-10
|
|
4
|
+
- Fixed mouse close prompt cursor move
|
|
5
|
+
- Fixed set filetype doesn't apply instantly
|
|
6
|
+
- Added syntax highlighting fallback if user ~/.config/micro yaml fails
|
|
7
|
+
- Redetect highlighting syntax when unknown filetype saves
|
|
8
|
+
- Warning for dos(CRLF) shell scripts
|
|
9
|
+
|
|
10
|
+
## [0.9.22] - 2026-06-09
|
|
11
|
+
- Clicking on icons toggles prompts
|
|
12
|
+
- Unsaved star triggers save cmd
|
|
13
|
+
- URLs support save cursor
|
|
14
|
+
|
|
3
15
|
## [0.9.21] - 2026-06-09
|
|
4
16
|
- Prompt mouse click repositions cursor (command and shell prompt)
|
|
5
17
|
- Clicking > or $ label toggles between command/shell prompt, preserving input
|
package/package.json
CHANGED
package/src/highlight/parser.js
CHANGED
|
@@ -47,25 +47,30 @@ export async function loadSyntaxDefinitions(runtime) {
|
|
|
47
47
|
const definitions = [];
|
|
48
48
|
for (const file of runtime.list(1)) {
|
|
49
49
|
let text = "";
|
|
50
|
+
let activeFile = file;
|
|
51
|
+
let source = null;
|
|
50
52
|
try {
|
|
51
53
|
text = await file.text();
|
|
54
|
+
source = Bun.YAML.parse(text);
|
|
52
55
|
} catch (e) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const fallback = file.real ? runtime.fallback?.(1, file.name) : null;
|
|
57
|
+
if (fallback) {
|
|
58
|
+
try {
|
|
59
|
+
text = await fallback.text();
|
|
60
|
+
source = Bun.YAML.parse(text);
|
|
61
|
+
activeFile = fallback;
|
|
62
|
+
console.error("Failed to load user syntax yaml, using built-in fallback:", file.name);
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
source = Bun.YAML.parse(text);
|
|
62
|
-
} catch (e) {
|
|
63
|
-
console.error("Failed to parse syntax yaml:", file.name);
|
|
67
|
+
if (!source) {
|
|
68
|
+
console.error("Failed to load syntax yaml:", file.name);
|
|
64
69
|
console.error(" Will not highlight this kind of file");
|
|
65
70
|
console.error(" @ loadSyntaxDefinitions ");
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
const header = headers.get(
|
|
73
|
+
const header = headers.get(activeFile.name) ?? (source ? parseHeaderYaml(source) : parseHeaderTextFallback(text, activeFile.name));
|
|
69
74
|
definitions.push(new SyntaxDefinition(header, source ?? { rules: [] }, text));
|
|
70
75
|
}
|
|
71
76
|
return definitions;
|
package/src/index.js
CHANGED
|
@@ -1092,6 +1092,7 @@ class BufferModel {
|
|
|
1092
1092
|
}
|
|
1093
1093
|
async save(path = this.path) {
|
|
1094
1094
|
if (!path) throw new Error("No filename");
|
|
1095
|
+
const detectSyntaxAfterSave = this.filetype === "unknown";
|
|
1095
1096
|
let text = this.lines.join("\n");
|
|
1096
1097
|
if (this.encoding === "hex3") {
|
|
1097
1098
|
await Bun.write(path, decodeBinaryBytes(Buffer.from(text, "latin1")));
|
|
@@ -1106,6 +1107,7 @@ class BufferModel {
|
|
|
1106
1107
|
this._savedSerial = this._undoSerial ?? 0;
|
|
1107
1108
|
this.modified = false;
|
|
1108
1109
|
this.message = `Saved ${path}`;
|
|
1110
|
+
if (detectSyntaxAfterSave && this._syntaxContext) attachSyntax(this, this._syntaxContext, path, text);
|
|
1109
1111
|
return;
|
|
1110
1112
|
}
|
|
1111
1113
|
if ((this.Settings.eofnewline ?? DEFAULT_SETTINGS.eofnewline) && !text.endsWith("\n")) text += "\n";
|
|
@@ -1123,6 +1125,7 @@ class BufferModel {
|
|
|
1123
1125
|
this._savedSerial = this._undoSerial ?? 0;
|
|
1124
1126
|
this.modified = false;
|
|
1125
1127
|
this.message = `Saved ${path}`;
|
|
1128
|
+
if (detectSyntaxAfterSave && this._syntaxContext) attachSyntax(this, this._syntaxContext, path, text);
|
|
1126
1129
|
}
|
|
1127
1130
|
|
|
1128
1131
|
// --- Autocomplete (BufferComplete) ---
|
|
@@ -1788,7 +1791,10 @@ class App {
|
|
|
1788
1791
|
const keymenuHeight = this.keymenu ? KEYDISPLAY.length : 0;
|
|
1789
1792
|
const activeSuggestions = this._activeSuggestions();
|
|
1790
1793
|
const activeSuggestionIdx = this._activeSuggestionIdx();
|
|
1791
|
-
const
|
|
1794
|
+
const formatWarning = this.buffer?.filetype === "shell" && this.buffer?.fileformat === "dos"
|
|
1795
|
+
? "dos(CRLF fileformat) invalid for shell scripts!"
|
|
1796
|
+
: "";
|
|
1797
|
+
const activeMessage = this.message || this.buffer?.message || formatWarning;
|
|
1792
1798
|
if (activeSuggestions.length === 0) this._acHScroll = 0;
|
|
1793
1799
|
const suggestionsHeight = activeSuggestions.length > 1 ? 1 : 0;
|
|
1794
1800
|
const messageHeight = suggestionsHeight ? 0 : activeMessage ? 1 : 0;
|
|
@@ -1884,8 +1890,14 @@ class App {
|
|
|
1884
1890
|
let sx = 0, x0;
|
|
1885
1891
|
// name
|
|
1886
1892
|
x0 = sx;
|
|
1887
|
-
sx = putText(this.screen, sx, statusRow, ` ${name}
|
|
1893
|
+
sx = putText(this.screen, sx, statusRow, ` ${name}`, isReadonlyBuffer(buf) ? redStatus : baseStatus, this.cols - sx);
|
|
1888
1894
|
markZone("name", x0, sx);
|
|
1895
|
+
if (dirty) {
|
|
1896
|
+
x0 = sx;
|
|
1897
|
+
sx = putText(this.screen, sx, statusRow, dirty, baseStatus, this.cols - sx);
|
|
1898
|
+
markZone("dirty", x0, sx);
|
|
1899
|
+
}
|
|
1900
|
+
sx = putText(this.screen, sx, statusRow, " ", baseStatus, this.cols - sx);
|
|
1889
1901
|
// (row,col)
|
|
1890
1902
|
sx = putText(this.screen, sx, statusRow, "(", baseStatus, this.cols - sx);
|
|
1891
1903
|
x0 = sx;
|
|
@@ -3478,6 +3490,12 @@ class App {
|
|
|
3478
3490
|
this.nextTab();
|
|
3479
3491
|
}
|
|
3480
3492
|
break;
|
|
3493
|
+
case "dirty": {
|
|
3494
|
+
const name = buf?.name ?? "No name";
|
|
3495
|
+
const filename = /^[^\s"'\\]+$/.test(name) ? name : JSON.stringify(name);
|
|
3496
|
+
this.openCommandMode(`save ${filename}`);
|
|
3497
|
+
break;
|
|
3498
|
+
}
|
|
3481
3499
|
case "row":
|
|
3482
3500
|
if (isTerm) {
|
|
3483
3501
|
this.pane.terminal?.write("\x12");
|
|
@@ -3505,18 +3523,7 @@ class App {
|
|
|
3505
3523
|
}
|
|
3506
3524
|
break;
|
|
3507
3525
|
case "ft": {
|
|
3508
|
-
|
|
3509
|
-
const filetypes = defs.map(d => d.filetype).filter(Boolean).sort();
|
|
3510
|
-
const ftComplete = (partial) => filetypes.filter(f => f.startsWith(partial));
|
|
3511
|
-
this.openPrompt("Set filetype: ", (value) => {
|
|
3512
|
-
if (!value || !buf) return;
|
|
3513
|
-
buf.filetype = value;
|
|
3514
|
-
buf.Settings.filetype = value;
|
|
3515
|
-
const def = defs.find(d => d.filetype === value);
|
|
3516
|
-
buf.syntaxDefinition = def ?? null;
|
|
3517
|
-
buf.highlighter = def ? new Highlighter(def, defs) : null;
|
|
3518
|
-
buf._highlightCache = null;
|
|
3519
|
-
}, { completer: ftComplete, initial: buf?.filetype ?? "" });
|
|
3526
|
+
this.openCommandMode("set filetype ");
|
|
3520
3527
|
break;
|
|
3521
3528
|
}
|
|
3522
3529
|
case "fmt":
|
|
@@ -3540,10 +3547,12 @@ class App {
|
|
|
3540
3547
|
await this.addTab();
|
|
3541
3548
|
break;
|
|
3542
3549
|
case "cmdmode":
|
|
3543
|
-
this.
|
|
3550
|
+
if (this.prompt?.type === "Command") this._suppressMouseUntilUp = true;
|
|
3551
|
+
await this.togglePromptMode("Command");
|
|
3544
3552
|
break;
|
|
3545
3553
|
case "shellmode":
|
|
3546
|
-
this.
|
|
3554
|
+
if (this.prompt?.type === "Shell") this._suppressMouseUntilUp = true;
|
|
3555
|
+
await this.togglePromptMode("Shell");
|
|
3547
3556
|
break;
|
|
3548
3557
|
}
|
|
3549
3558
|
return true;
|
|
@@ -3753,6 +3762,18 @@ class App {
|
|
|
3753
3762
|
this.prompt = new Prompt(label, callback, { yn: true, onCancel });
|
|
3754
3763
|
}
|
|
3755
3764
|
|
|
3765
|
+
async togglePromptMode(type) {
|
|
3766
|
+
if (this.prompt?.type === type) {
|
|
3767
|
+
const prompt = this.prompt;
|
|
3768
|
+
this.prompt = null;
|
|
3769
|
+
await prompt.onCancel?.();
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
if (type === "Command") this.openCommandMode();
|
|
3774
|
+
else this.openShellMode();
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3756
3777
|
async checkExternalReload() {
|
|
3757
3778
|
if (this.prompt || this.pane?.type !== "editor") return false;
|
|
3758
3779
|
const buf = this.buffer;
|
|
@@ -5125,6 +5146,18 @@ function completeOptionValue(cmd, option, partial, context) {
|
|
|
5125
5146
|
const optVal = allSettings[option];
|
|
5126
5147
|
const suggestions = [];
|
|
5127
5148
|
|
|
5149
|
+
if (option === "filetype") {
|
|
5150
|
+
const filetypes = [
|
|
5151
|
+
"off",
|
|
5152
|
+
"unknown",
|
|
5153
|
+
...(context?.syntaxDefinitions ?? []).map((definition) => definition.filetype),
|
|
5154
|
+
];
|
|
5155
|
+
return [...new Set(filetypes)]
|
|
5156
|
+
.filter((filetype) => filetype && filetype.startsWith(partial))
|
|
5157
|
+
.sort()
|
|
5158
|
+
.map((filetype) => ({ value: `${cmd} ${option} ${filetype}`, label: filetype }));
|
|
5159
|
+
}
|
|
5160
|
+
|
|
5128
5161
|
if (typeof optVal === "boolean") {
|
|
5129
5162
|
if ("on".startsWith(partial)) suggestions.push("on");
|
|
5130
5163
|
else if ("true".startsWith(partial)) suggestions.push("true");
|
|
@@ -5649,17 +5682,18 @@ function commandHasStartupJump(command = {}) {
|
|
|
5649
5682
|
}
|
|
5650
5683
|
|
|
5651
5684
|
async function loadBufferForPath(pathOrUrl, context, command = {}) {
|
|
5685
|
+
let buffer;
|
|
5652
5686
|
if (isHttpUrl(pathOrUrl)) {
|
|
5653
5687
|
let encoding = context.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
|
|
5654
5688
|
const decoded = await fetchTextWithEncoding(pathOrUrl, encoding);
|
|
5655
5689
|
const text = decoded.text;
|
|
5656
5690
|
encoding = decoded.encoding;
|
|
5657
5691
|
const urlPath = pathOrUrl.replace(/[?#].*$/, "");
|
|
5658
|
-
|
|
5692
|
+
buffer = new BufferModel({ path: pathOrUrl, text, command, encoding });
|
|
5659
5693
|
attachSyntax(buffer, context, urlPath, text);
|
|
5660
|
-
|
|
5694
|
+
} else {
|
|
5695
|
+
buffer = await BufferModel.fromFile(pathOrUrl, command, context);
|
|
5661
5696
|
}
|
|
5662
|
-
const buffer = await BufferModel.fromFile(pathOrUrl, command, context);
|
|
5663
5697
|
if (DEFAULT_SETTINGS.savecursor && !commandHasStartupJump(command) && context?.cursorStates?.[pathOrUrl]) {
|
|
5664
5698
|
const saved = context.cursorStates[pathOrUrl];
|
|
5665
5699
|
const y = clamp(saved.y ?? 0, 0, buffer.lines.length - 1);
|
|
@@ -6204,9 +6238,14 @@ async function loadBuffers(files, command) {
|
|
|
6204
6238
|
if (loadBuffers.context) attachSyntax(stdinBuf, loadBuffers.context, "", stdinText);
|
|
6205
6239
|
buffers.push(stdinBuf);
|
|
6206
6240
|
} else {
|
|
6207
|
-
|
|
6241
|
+
const buffer = new BufferModel({ command });
|
|
6242
|
+
if (loadBuffers.context) attachSyntax(buffer, loadBuffers.context, "", "");
|
|
6243
|
+
buffers.push(buffer);
|
|
6208
6244
|
}
|
|
6209
|
-
|
|
6245
|
+
if (buffers.length > 0) return buffers;
|
|
6246
|
+
const buffer = new BufferModel({ command });
|
|
6247
|
+
if (loadBuffers.context) attachSyntax(buffer, loadBuffers.context, "", "");
|
|
6248
|
+
return [buffer];
|
|
6210
6249
|
}
|
|
6211
6250
|
|
|
6212
6251
|
async function printReadmeDocs() {
|
|
@@ -6641,6 +6680,7 @@ function getSelectionText(buf, selection) {
|
|
|
6641
6680
|
}
|
|
6642
6681
|
|
|
6643
6682
|
function attachSyntax(buffer, context, path, text) {
|
|
6683
|
+
buffer._syntaxContext = context;
|
|
6644
6684
|
const def = detectBufferSyntax(context.syntaxDefinitions, path, text);
|
|
6645
6685
|
buffer.syntaxDefinition = def;
|
|
6646
6686
|
buffer.filetype = def?.filetype ?? "unknown";
|
|
@@ -6648,21 +6688,32 @@ function attachSyntax(buffer, context, path, text) {
|
|
|
6648
6688
|
buffer.highlighter = def ? new Highlighter(def, context.syntaxDefinitions ?? []) : null;
|
|
6649
6689
|
buffer._highlightCache = null;
|
|
6650
6690
|
buffer._onOptionChange = (option, oldVal, newVal) => {
|
|
6691
|
+
if (option === "filetype") setBufferFiletype(buffer, context, newVal);
|
|
6651
6692
|
const ba = makeBufferAdapter(buffer);
|
|
6652
6693
|
context.plugins?.run("onBufferOptionChanged", ba, option, oldVal, newVal);
|
|
6653
6694
|
context.jsPlugins?.run("onBufferOptionChanged", ba, option, oldVal, newVal);
|
|
6654
6695
|
};
|
|
6655
6696
|
}
|
|
6656
6697
|
|
|
6698
|
+
function setBufferFiletype(buffer, context, filetype) {
|
|
6699
|
+
const value = String(filetype);
|
|
6700
|
+
const definitions = context?.syntaxDefinitions ?? [];
|
|
6701
|
+
const def = definitions.find((candidate) => candidate.filetype === value) ?? null;
|
|
6702
|
+
buffer.filetype = value;
|
|
6703
|
+
buffer.Settings.filetype = value;
|
|
6704
|
+
buffer.syntaxDefinition = def;
|
|
6705
|
+
buffer.highlighter = def ? new Highlighter(def, definitions) : null;
|
|
6706
|
+
buffer._highlightCache = null;
|
|
6707
|
+
}
|
|
6708
|
+
|
|
6657
6709
|
function detectBufferSyntax(definitions, path, text) {
|
|
6658
6710
|
if (!definitions) return null;
|
|
6659
|
-
const lines =
|
|
6711
|
+
const lines = normalizeBufferText(text).split("\n").slice(0, 50);
|
|
6660
6712
|
return detectSyntax(definitions, { path, firstLine: lines[0] ?? "", lines });
|
|
6661
6713
|
}
|
|
6662
6714
|
|
|
6663
6715
|
function detectBufferFiletype(definitions, path, text) {
|
|
6664
6716
|
if (!definitions) return "unknown";
|
|
6665
|
-
const lines = String(text).split("\n").slice(0, 50);
|
|
6666
6717
|
return detectBufferSyntax(definitions, path, text)?.filetype ?? "unknown";
|
|
6667
6718
|
}
|
|
6668
6719
|
|
|
@@ -6736,7 +6787,7 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
|
|
|
6736
6787
|
);
|
|
6737
6788
|
continue;
|
|
6738
6789
|
}
|
|
6739
|
-
const lines = content.split("\n");
|
|
6790
|
+
const lines = normalizeBufferText(content).split("\n");
|
|
6740
6791
|
const def = detectSyntax(syntaxDefinitions, {
|
|
6741
6792
|
path: effectivePath ?? "",
|
|
6742
6793
|
firstLine: lines[0] ?? "",
|
package/src/runtime/registry.js
CHANGED
|
@@ -14,11 +14,13 @@ export class RuntimeRegistry {
|
|
|
14
14
|
this.configDir = configDir;
|
|
15
15
|
this.files = [[], [], [], [], []];
|
|
16
16
|
this.realFiles = [[], [], [], [], []];
|
|
17
|
+
this.fallbackFiles = [[], [], [], [], []];
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
async init({ user = true } = {}) {
|
|
20
21
|
this.files = [[], [], [], [], []];
|
|
21
22
|
this.realFiles = [[], [], [], [], []];
|
|
23
|
+
this.fallbackFiles = [[], [], [], [], []];
|
|
22
24
|
await this.addRuntimeKind(RTColorscheme, "colorschemes", ".micro", user);
|
|
23
25
|
await this.addRuntimeKind(RTSyntax, "syntax", ".yaml", user);
|
|
24
26
|
await this.addRuntimeKind(RTSyntaxHeader, "syntax", ".hdr", user);
|
|
@@ -36,7 +38,10 @@ export class RuntimeRegistry {
|
|
|
36
38
|
for (const entry of entries) {
|
|
37
39
|
if (entry.isDirectory() || !entry.name.endsWith(extension)) continue;
|
|
38
40
|
const file = new RuntimeFile(join(dir, entry.name), real);
|
|
39
|
-
if (!real && this.realFiles[kind].some((f) => f.name === file.name))
|
|
41
|
+
if (!real && this.realFiles[kind].some((f) => f.name === file.name)) {
|
|
42
|
+
this.fallbackFiles[kind].push(file);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
40
45
|
this.files[kind].push(file);
|
|
41
46
|
if (real) this.realFiles[kind].push(file);
|
|
42
47
|
}
|
|
@@ -53,6 +58,10 @@ export class RuntimeRegistry {
|
|
|
53
58
|
find(kind, name) {
|
|
54
59
|
return this.list(kind).find((file) => file.name === name) ?? null;
|
|
55
60
|
}
|
|
61
|
+
|
|
62
|
+
fallback(kind, name) {
|
|
63
|
+
return this.fallbackFiles[kind]?.find((file) => file.name === name) ?? null;
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
class RuntimeFile {
|