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 +20 -0
- package/package.json +1 -1
- package/runtime/help/options.md +8 -0
- 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 +8 -1
- package/src/index.js +530 -152
- package/src/platform/clipboard.js +125 -7
- package/src/plugins/js-bridge.js +10 -8
- package/src/screen/screen.js +16 -4
- package/todo.txt +8 -3
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
package/runtime/help/options.md
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/config/config.js
CHANGED
|
@@ -104,7 +104,9 @@ function normalizeSetting(key, value) {
|
|
|
104
104
|
|
|
105
105
|
function validateOption(option, value) {
|
|
106
106
|
if (option === "encoding") {
|
|
107
|
-
|
|
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];
|
package/src/config/defaults.js
CHANGED
|
@@ -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 };
|