bunmicro 0.9.22 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## [0.9.22] - 2026-06-09
4
11
  - Clicking on icons toggles prompts
5
12
  - Unsaved star triggers save cmd
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunmicro",
3
- "version": "0.9.22",
3
+ "version": "0.9.23",
4
4
  "description": "Bun JavaScript rewrite of the micro editor originally in Golang",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -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
- console.error("Failed to read syntax yaml:", file.name);
54
- console.error(" Will not highlight this kind of file");
55
- console.error(" @ loadSyntaxDefinitions ");
56
- continue;
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
- let source = null;
60
- try {
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(file.name) ?? (source ? parseHeaderYaml(source) : parseHeaderTextFallback(text, file.name));
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 activeMessage = this.message || this.buffer?.message || "";
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;
@@ -3517,18 +3523,7 @@ class App {
3517
3523
  }
3518
3524
  break;
3519
3525
  case "ft": {
3520
- const defs = this.context.syntaxDefinitions ?? [];
3521
- const filetypes = defs.map(d => d.filetype).filter(Boolean).sort();
3522
- const ftComplete = (partial) => filetypes.filter(f => f.startsWith(partial));
3523
- this.openPrompt("Set filetype: ", (value) => {
3524
- if (!value || !buf) return;
3525
- buf.filetype = value;
3526
- buf.Settings.filetype = value;
3527
- const def = defs.find(d => d.filetype === value);
3528
- buf.syntaxDefinition = def ?? null;
3529
- buf.highlighter = def ? new Highlighter(def, defs) : null;
3530
- buf._highlightCache = null;
3531
- }, { completer: ftComplete, initial: buf?.filetype ?? "" });
3526
+ this.openCommandMode("set filetype ");
3532
3527
  break;
3533
3528
  }
3534
3529
  case "fmt":
@@ -3552,9 +3547,11 @@ class App {
3552
3547
  await this.addTab();
3553
3548
  break;
3554
3549
  case "cmdmode":
3550
+ if (this.prompt?.type === "Command") this._suppressMouseUntilUp = true;
3555
3551
  await this.togglePromptMode("Command");
3556
3552
  break;
3557
3553
  case "shellmode":
3554
+ if (this.prompt?.type === "Shell") this._suppressMouseUntilUp = true;
3558
3555
  await this.togglePromptMode("Shell");
3559
3556
  break;
3560
3557
  }
@@ -5149,6 +5146,18 @@ function completeOptionValue(cmd, option, partial, context) {
5149
5146
  const optVal = allSettings[option];
5150
5147
  const suggestions = [];
5151
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
+
5152
5161
  if (typeof optVal === "boolean") {
5153
5162
  if ("on".startsWith(partial)) suggestions.push("on");
5154
5163
  else if ("true".startsWith(partial)) suggestions.push("true");
@@ -6229,9 +6238,14 @@ async function loadBuffers(files, command) {
6229
6238
  if (loadBuffers.context) attachSyntax(stdinBuf, loadBuffers.context, "", stdinText);
6230
6239
  buffers.push(stdinBuf);
6231
6240
  } else {
6232
- buffers.push(new BufferModel({ command }));
6241
+ const buffer = new BufferModel({ command });
6242
+ if (loadBuffers.context) attachSyntax(buffer, loadBuffers.context, "", "");
6243
+ buffers.push(buffer);
6233
6244
  }
6234
- return buffers.length ? buffers : [new BufferModel({ command })];
6245
+ if (buffers.length > 0) return buffers;
6246
+ const buffer = new BufferModel({ command });
6247
+ if (loadBuffers.context) attachSyntax(buffer, loadBuffers.context, "", "");
6248
+ return [buffer];
6235
6249
  }
6236
6250
 
6237
6251
  async function printReadmeDocs() {
@@ -6666,6 +6680,7 @@ function getSelectionText(buf, selection) {
6666
6680
  }
6667
6681
 
6668
6682
  function attachSyntax(buffer, context, path, text) {
6683
+ buffer._syntaxContext = context;
6669
6684
  const def = detectBufferSyntax(context.syntaxDefinitions, path, text);
6670
6685
  buffer.syntaxDefinition = def;
6671
6686
  buffer.filetype = def?.filetype ?? "unknown";
@@ -6673,21 +6688,32 @@ function attachSyntax(buffer, context, path, text) {
6673
6688
  buffer.highlighter = def ? new Highlighter(def, context.syntaxDefinitions ?? []) : null;
6674
6689
  buffer._highlightCache = null;
6675
6690
  buffer._onOptionChange = (option, oldVal, newVal) => {
6691
+ if (option === "filetype") setBufferFiletype(buffer, context, newVal);
6676
6692
  const ba = makeBufferAdapter(buffer);
6677
6693
  context.plugins?.run("onBufferOptionChanged", ba, option, oldVal, newVal);
6678
6694
  context.jsPlugins?.run("onBufferOptionChanged", ba, option, oldVal, newVal);
6679
6695
  };
6680
6696
  }
6681
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
+
6682
6709
  function detectBufferSyntax(definitions, path, text) {
6683
6710
  if (!definitions) return null;
6684
- const lines = String(text).split("\n").slice(0, 50);
6711
+ const lines = normalizeBufferText(text).split("\n").slice(0, 50);
6685
6712
  return detectSyntax(definitions, { path, firstLine: lines[0] ?? "", lines });
6686
6713
  }
6687
6714
 
6688
6715
  function detectBufferFiletype(definitions, path, text) {
6689
6716
  if (!definitions) return "unknown";
6690
- const lines = String(text).split("\n").slice(0, 50);
6691
6717
  return detectBufferSyntax(definitions, path, text)?.filetype ?? "unknown";
6692
6718
  }
6693
6719
 
@@ -6761,7 +6787,7 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
6761
6787
  );
6762
6788
  continue;
6763
6789
  }
6764
- const lines = content.split("\n");
6790
+ const lines = normalizeBufferText(content).split("\n");
6765
6791
  const def = detectSyntax(syntaxDefinitions, {
6766
6792
  path: effectivePath ?? "",
6767
6793
  firstLine: lines[0] ?? "",
@@ -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)) continue;
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 {