bunmicro 0.9.10 → 0.9.20

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,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.20] - 2026-06-07
4
+ - Added cursor shape option
5
+ - Fixed selection hide cursor
6
+
7
+ ## [0.9.19] - 2026-06-06
8
+ - Fixed Windows clipboard
9
+ - Fixed cli encoding help readme
10
+ - Added cli clean config dir
11
+ - Added softwrap subrow line number
12
+ * goto line
13
+ * show subrow line number in showpath
14
+ * pageup/down respects softwrap
15
+ - Fixed CRLF being overwritten
16
+ - Added primary clipboard
17
+ - Fixed clipboard cmd / action
18
+ * Ctrl+K should accumulate
19
+ - Clipboard backend priority
20
+ - Added OSC 52 clipboard copy
21
+ * Use by set clipboard terminal
22
+
3
23
  ## [0.9.10] - 2026-06-04
4
24
  - Up/Down key auto-complete selection in editor
5
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunmicro",
3
- "version": "0.9.10",
3
+ "version": "0.9.20",
4
4
  "description": "Bun JavaScript rewrite of the micro editor originally in Golang",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -94,6 +94,13 @@ Here are the available options:
94
94
 
95
95
  default value: `true`
96
96
 
97
+ * `cursorshape`: sets the editor cursor shape using the terminal's DECSCUSR
98
+ support. This setting is `global only`. Supported values are `default`,
99
+ `block`, `underline`, `bar`, and their `blinking-` variants. The plain
100
+ shape names use a steady cursor.
101
+
102
+ default value: `block`
103
+
97
104
  * `detectlimit`: if this is not set to 0, it will limit the amount of first
98
105
  lines in a file that are matched to determine the filetype.
99
106
  A higher limit means better accuracy of guessing the filetype, but also
@@ -566,6 +573,7 @@ so that you can see what the formatting should look like.
566
573
  "colorscheme": "default",
567
574
  "comment": true,
568
575
  "cursorline": true,
576
+ "cursorshape": "block",
569
577
  "detectlimit": 100,
570
578
  "diff": true,
571
579
  "diffgutter": false,
@@ -61,7 +61,9 @@ micro.on("init", () => {
61
61
  //micro.TermMessage("Hello from JS plugin! Args: " + args.join(", "));
62
62
 
63
63
  //await micro.alert(micro.getLine())
64
- await micro.alert(bp?.Buf?.Path || "(no path)")
64
+ const path = bp?.Buf?.Path || "(no path)";
65
+ const loc = bp?.CursorLocation?.() || "+1.0:1";
66
+ await micro.alert(`${path}\n${loc}`)
65
67
 
66
68
  });
67
69
 
@@ -0,0 +1,172 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { defaultAllSettings, LOCAL_SETTINGS } from "./defaults.js";
6
+
7
+ function settingsEqual(a, b) {
8
+ return JSON.stringify(a) === JSON.stringify(b);
9
+ }
10
+
11
+ async function createCleanPrompt() {
12
+ if (process.stdin.isTTY) {
13
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
14
+ return {
15
+ ask: async () => {
16
+ const text = (await rl.question("Continue [Y/n]: ")).trim();
17
+ return text.length === 0 || text.toLowerCase().startsWith("y");
18
+ },
19
+ close: () => rl.close(),
20
+ };
21
+ }
22
+
23
+ const answers = (await Bun.stdin.text()).split(/\r?\n/);
24
+ let index = 0;
25
+ return {
26
+ ask: async () => {
27
+ process.stdout.write("Continue [Y/n]: ");
28
+ const text = (answers[index++] ?? "").trim();
29
+ return text.length === 0 || text.toLowerCase().startsWith("y");
30
+ },
31
+ close: () => {},
32
+ };
33
+ }
34
+
35
+ function cleanDefaultSettings(config) {
36
+ const defaults = defaultAllSettings();
37
+ const cleaned = { ...config.parsedSettings };
38
+ for (const [key, value] of Object.entries(cleaned)) {
39
+ if (LOCAL_SETTINGS.has(key)) {
40
+ delete cleaned[key];
41
+ continue;
42
+ }
43
+ if (key in defaults && settingsEqual(config.globalSettings[key], defaults[key]) && settingsEqual(value, defaults[key])) {
44
+ delete cleaned[key];
45
+ }
46
+ }
47
+ return cleaned;
48
+ }
49
+
50
+ function findUnusedOptions(settings, pluginNames) {
51
+ const defaults = defaultAllSettings();
52
+ const unused = [];
53
+ for (const key of Object.keys(settings)) {
54
+ if (key.startsWith("ft:") || key.startsWith("glob:")) continue;
55
+ if (key in defaults) continue;
56
+ let valid = false;
57
+ for (const name of pluginNames) {
58
+ if (key === name || key.startsWith(`${name}.`)) {
59
+ valid = true;
60
+ break;
61
+ }
62
+ }
63
+ if (!valid) unused.push(key);
64
+ }
65
+ return unused.sort();
66
+ }
67
+
68
+ async function writeCleanSettings(config, settings) {
69
+ const settingsFile = join(config.configDir, "settings.json");
70
+ await Bun.write(settingsFile, JSON.stringify(settings, null, " ") + "\n");
71
+ }
72
+
73
+ function invalidBunBufferStateFile(path, name) {
74
+ if (name !== "history.json" && name !== "cursor_state.json") return false;
75
+ try {
76
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
77
+ return !parsed || typeof parsed !== "object" || Array.isArray(parsed);
78
+ } catch {
79
+ return true;
80
+ }
81
+ }
82
+
83
+ export async function cleanConfig(config, plugins) {
84
+ const prompt = await createCleanPrompt();
85
+ try {
86
+ console.log("Cleaning your configuration directory at", config.configDir);
87
+ console.log(`Please consider backing up ${config.configDir} before continuing`);
88
+
89
+ if (!(await prompt.ask())) {
90
+ console.log("Stopping early");
91
+ return;
92
+ }
93
+
94
+ console.log("Cleaning default settings");
95
+ let cleanedSettings = cleanDefaultSettings(config);
96
+ try {
97
+ await writeCleanSettings(config, cleanedSettings);
98
+ } catch (err) {
99
+ console.log(`Error writing settings.json file: ${err.message}`);
100
+ }
101
+
102
+ const pluginNames = new Set(plugins.list().map((plugin) => plugin.name));
103
+ const unusedOptions = findUnusedOptions(cleanedSettings, pluginNames);
104
+ if (unusedOptions.length > 0) {
105
+ const settingsFile = join(config.configDir, "settings.json");
106
+ console.log("The following options are unused:");
107
+ for (const option of unusedOptions) console.log(`${option} (value: ${JSON.stringify(cleanedSettings[option])})`);
108
+ console.log(`These options will be removed from ${settingsFile}`);
109
+
110
+ if (await prompt.ask()) {
111
+ for (const option of unusedOptions) delete cleanedSettings[option];
112
+ try {
113
+ await writeCleanSettings(config, cleanedSettings);
114
+ console.log("Removed unused options");
115
+ console.log("\n");
116
+ } catch (err) {
117
+ console.log(`Error overwriting settings.json file: ${err.message}`);
118
+ }
119
+ }
120
+ }
121
+
122
+ const buffersPath = join(config.configDir, "buffers");
123
+ if (existsSync(buffersPath)) {
124
+ const badFiles = [];
125
+ for (const entry of readdirSync(buffersPath, { withFileTypes: true })) {
126
+ if (!entry.isFile()) continue;
127
+ const path = join(buffersPath, entry.name);
128
+ if (invalidBunBufferStateFile(path, entry.name)) badFiles.push(path);
129
+ }
130
+ if (badFiles.length > 0) {
131
+ console.log(`Detected ${badFiles.length} files with an invalid format in ${buffersPath}`);
132
+ console.log("These files store cursor and undo history.");
133
+ console.log(`Removing badly formatted files in ${buffersPath}`);
134
+
135
+ if (await prompt.ask()) {
136
+ let removed = 0;
137
+ for (const file of badFiles) {
138
+ try {
139
+ await rm(file);
140
+ removed++;
141
+ } catch (err) {
142
+ console.log(err.message);
143
+ }
144
+ }
145
+ if (removed === 0) console.log("Failed to remove files");
146
+ else console.log(`Removed ${removed} badly formatted files`);
147
+ console.log("\n");
148
+ }
149
+ }
150
+ }
151
+
152
+ const oldPluginsDir = join(config.configDir, "plugins");
153
+ if (existsSync(oldPluginsDir) && statSync(oldPluginsDir).isDirectory()) {
154
+ console.log(`Found directory ${oldPluginsDir}`);
155
+ console.log(`Plugins should now be stored in ${join(config.configDir, "plug")}`);
156
+ console.log(`Removing ${oldPluginsDir}`);
157
+
158
+ if (await prompt.ask()) {
159
+ try {
160
+ await rm(oldPluginsDir, { recursive: true, force: true });
161
+ } catch (err) {
162
+ console.log(err.message);
163
+ }
164
+ }
165
+ console.log("\n");
166
+ }
167
+
168
+ console.log("Done cleaning");
169
+ } finally {
170
+ prompt.close();
171
+ }
172
+ }
@@ -104,7 +104,9 @@ function normalizeSetting(key, value) {
104
104
 
105
105
  function validateOption(option, value) {
106
106
  if (option === "encoding") {
107
- try { new TextDecoder(String(value || "utf-8")); }
107
+ const encoding = String(value || "utf-8");
108
+ if (encoding === "hex3") return;
109
+ try { new TextDecoder(encoding); }
108
110
  catch { throw new Error(`Invalid encoding: ${value}`); }
109
111
  }
110
112
  const choices = OPTION_CHOICES[option];
@@ -57,6 +57,7 @@ export const DEFAULT_GLOBAL_ONLY_SETTINGS = {
57
57
  autosave: 0,
58
58
  clipboard: "external",
59
59
  colorscheme: "default",
60
+ cursorshape: "block",
60
61
  savehistory: true,
61
62
  divchars: "|-",
62
63
  divreverse: true,
@@ -81,6 +82,12 @@ export const DEFAULT_GLOBAL_ONLY_SETTINGS = {
81
82
 
82
83
  export const OPTION_CHOICES = {
83
84
  clipboard: ["internal", "external", "terminal"],
85
+ cursorshape: [
86
+ "default",
87
+ "block", "blinking-block",
88
+ "underline", "blinking-underline",
89
+ "bar", "blinking-bar",
90
+ ],
84
91
  fileformat: ["unix", "dos"],
85
92
  helpsplit: ["hsplit", "vsplit"],
86
93
  matchbracestyle: ["underline", "highlight"],
@@ -91,7 +98,7 @@ export const OPTION_CHOICES = {
91
98
 
92
99
  // Settings that are buffer-local only and must never be written to the global config file.
93
100
  // Mirrors Go micro's config.LocalSettings.
94
- export const LOCAL_SETTINGS = new Set(["readonly", "filetype"]);
101
+ export const LOCAL_SETTINGS = new Set(["readonly", "filetype", "fileformat"]);
95
102
 
96
103
  export function defaultAllSettings() {
97
104
  return { ...DEFAULT_COMMON_SETTINGS, ...DEFAULT_GLOBAL_ONLY_SETTINGS };