jsmdcui 0.3.0

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.
Files changed (258) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/LICENSE +22 -0
  3. package/README.md +285 -0
  4. package/package.json +17 -0
  5. package/project_structure.txt +35 -0
  6. package/runmd.mjs +240 -0
  7. package/runtime/colorschemes/atom-dark.micro +33 -0
  8. package/runtime/colorschemes/bubblegum.micro +31 -0
  9. package/runtime/colorschemes/cmc-16.micro +47 -0
  10. package/runtime/colorschemes/cmc-tc.micro +43 -0
  11. package/runtime/colorschemes/darcula.micro +34 -0
  12. package/runtime/colorschemes/default.micro +1 -0
  13. package/runtime/colorschemes/dracula-tc.micro +49 -0
  14. package/runtime/colorschemes/dukedark-tc.micro +38 -0
  15. package/runtime/colorschemes/dukelight-tc.micro +38 -0
  16. package/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
  17. package/runtime/colorschemes/geany.micro +29 -0
  18. package/runtime/colorschemes/gotham.micro +29 -0
  19. package/runtime/colorschemes/gruvbox-tc.micro +29 -0
  20. package/runtime/colorschemes/gruvbox.micro +26 -0
  21. package/runtime/colorschemes/material-tc.micro +36 -0
  22. package/runtime/colorschemes/monokai-dark.micro +28 -0
  23. package/runtime/colorschemes/monokai.micro +34 -0
  24. package/runtime/colorschemes/one-dark.micro +39 -0
  25. package/runtime/colorschemes/railscast.micro +37 -0
  26. package/runtime/colorschemes/simple.micro +33 -0
  27. package/runtime/colorschemes/solarized-tc.micro +31 -0
  28. package/runtime/colorschemes/solarized.micro +30 -0
  29. package/runtime/colorschemes/sunny-day.micro +29 -0
  30. package/runtime/colorschemes/twilight.micro +40 -0
  31. package/runtime/colorschemes/zenburn.micro +30 -0
  32. package/runtime/help/actions.md +161 -0
  33. package/runtime/help/cdp.md +119 -0
  34. package/runtime/help/colors.md +421 -0
  35. package/runtime/help/commands.md +161 -0
  36. package/runtime/help/copypaste.md +149 -0
  37. package/runtime/help/defaultkeys.md +148 -0
  38. package/runtime/help/help.md +285 -0
  39. package/runtime/help/keybindings.md +355 -0
  40. package/runtime/help/linter.md +90 -0
  41. package/runtime/help/options.md +709 -0
  42. package/runtime/help/plugins.md +544 -0
  43. package/runtime/help/tutorial.md +112 -0
  44. package/runtime/jsplugins/cdp/cdp-server.js +1161 -0
  45. package/runtime/jsplugins/cdp/cdp.js +192 -0
  46. package/runtime/jsplugins/chapter/chapter.js +108 -0
  47. package/runtime/jsplugins/diff/diff.js +46 -0
  48. package/runtime/jsplugins/example/example.js +110 -0
  49. package/runtime/jsplugins/linter/linter.js +281 -0
  50. package/runtime/plugins/autoclose/autoclose.lua +75 -0
  51. package/runtime/plugins/ftoptions/ftoptions.lua +17 -0
  52. package/runtime/plugins/literate/README.md +5 -0
  53. package/runtime/plugins/literate/literate.lua +55 -0
  54. package/runtime/plugins/status/help/status.md +21 -0
  55. package/runtime/plugins/status/status.lua +62 -0
  56. package/runtime/syntax/LICENSE +22 -0
  57. package/runtime/syntax/PowerShell.yaml +128 -0
  58. package/runtime/syntax/README.md +63 -0
  59. package/runtime/syntax/ada.yaml +43 -0
  60. package/runtime/syntax/apacheconf.yaml +59 -0
  61. package/runtime/syntax/arduino.yaml +101 -0
  62. package/runtime/syntax/asciidoc.yaml +51 -0
  63. package/runtime/syntax/asm.yaml +123 -0
  64. package/runtime/syntax/ats.yaml +99 -0
  65. package/runtime/syntax/awk.yaml +44 -0
  66. package/runtime/syntax/b.yaml +87 -0
  67. package/runtime/syntax/bat.yaml +57 -0
  68. package/runtime/syntax/c.yaml +60 -0
  69. package/runtime/syntax/caddyfile.yaml +23 -0
  70. package/runtime/syntax/cake.yaml +7 -0
  71. package/runtime/syntax/clojure.yaml +38 -0
  72. package/runtime/syntax/cmake.yaml +42 -0
  73. package/runtime/syntax/coffeescript.yaml +56 -0
  74. package/runtime/syntax/colortest.yaml +19 -0
  75. package/runtime/syntax/conky.yaml +17 -0
  76. package/runtime/syntax/cpp.yaml +91 -0
  77. package/runtime/syntax/crontab.yaml +36 -0
  78. package/runtime/syntax/crystal.yaml +72 -0
  79. package/runtime/syntax/csharp.yaml +52 -0
  80. package/runtime/syntax/css.yaml +44 -0
  81. package/runtime/syntax/csx.yaml +8 -0
  82. package/runtime/syntax/cuda.yaml +68 -0
  83. package/runtime/syntax/cython.yaml +52 -0
  84. package/runtime/syntax/d.yaml +121 -0
  85. package/runtime/syntax/dart.yaml +46 -0
  86. package/runtime/syntax/default.yaml +10 -0
  87. package/runtime/syntax/dockerfile.yaml +36 -0
  88. package/runtime/syntax/dot.yaml +29 -0
  89. package/runtime/syntax/elixir.yaml +30 -0
  90. package/runtime/syntax/elm.yaml +38 -0
  91. package/runtime/syntax/erb.yaml +42 -0
  92. package/runtime/syntax/erlang.yaml +45 -0
  93. package/runtime/syntax/fish.yaml +48 -0
  94. package/runtime/syntax/forth.yaml +34 -0
  95. package/runtime/syntax/fortran.yaml +64 -0
  96. package/runtime/syntax/freebsd-kernel.yaml +14 -0
  97. package/runtime/syntax/fsharp.yaml +48 -0
  98. package/runtime/syntax/gdscript.yaml +61 -0
  99. package/runtime/syntax/gemini.yaml +19 -0
  100. package/runtime/syntax/gentoo-ebuild.yaml +48 -0
  101. package/runtime/syntax/gentoo-etc-portage.yaml +23 -0
  102. package/runtime/syntax/git-commit.yaml +35 -0
  103. package/runtime/syntax/git-config.yaml +14 -0
  104. package/runtime/syntax/git-rebase-todo.yaml +19 -0
  105. package/runtime/syntax/gleam.yaml +69 -0
  106. package/runtime/syntax/glsl.yaml +26 -0
  107. package/runtime/syntax/gnuplot.yaml +15 -0
  108. package/runtime/syntax/go.yaml +62 -0
  109. package/runtime/syntax/godoc.yaml +17 -0
  110. package/runtime/syntax/golo.yaml +73 -0
  111. package/runtime/syntax/gomod.yaml +31 -0
  112. package/runtime/syntax/graphql.yaml +47 -0
  113. package/runtime/syntax/groff.yaml +30 -0
  114. package/runtime/syntax/groovy.yaml +111 -0
  115. package/runtime/syntax/haml.yaml +16 -0
  116. package/runtime/syntax/hare.yaml +52 -0
  117. package/runtime/syntax/haskell.yaml +52 -0
  118. package/runtime/syntax/hc.yaml +52 -0
  119. package/runtime/syntax/html.yaml +70 -0
  120. package/runtime/syntax/html4.yaml +25 -0
  121. package/runtime/syntax/html5.yaml +25 -0
  122. package/runtime/syntax/ini.yaml +23 -0
  123. package/runtime/syntax/inputrc.yaml +14 -0
  124. package/runtime/syntax/java.yaml +37 -0
  125. package/runtime/syntax/javascript.yaml +124 -0
  126. package/runtime/syntax/jinja2.yaml +19 -0
  127. package/runtime/syntax/json.yaml +39 -0
  128. package/runtime/syntax/jsonnet.yaml +92 -0
  129. package/runtime/syntax/julia.yaml +57 -0
  130. package/runtime/syntax/justfile.yaml +40 -0
  131. package/runtime/syntax/keymap.yaml +27 -0
  132. package/runtime/syntax/kickstart.yaml +16 -0
  133. package/runtime/syntax/kotlin.yaml +66 -0
  134. package/runtime/syntax/kvlang.yaml +67 -0
  135. package/runtime/syntax/ledger.yaml +14 -0
  136. package/runtime/syntax/lfe.yaml +17 -0
  137. package/runtime/syntax/lilypond.yaml +26 -0
  138. package/runtime/syntax/lisp.yaml +17 -0
  139. package/runtime/syntax/log.yaml +92 -0
  140. package/runtime/syntax/lua.yaml +111 -0
  141. package/runtime/syntax/mail.yaml +25 -0
  142. package/runtime/syntax/makefile.yaml +38 -0
  143. package/runtime/syntax/man.yaml +12 -0
  144. package/runtime/syntax/markdown.yaml +49 -0
  145. package/runtime/syntax/mc.yaml +23 -0
  146. package/runtime/syntax/meson.yaml +51 -0
  147. package/runtime/syntax/micro.yaml +34 -0
  148. package/runtime/syntax/mpdconf.yaml +13 -0
  149. package/runtime/syntax/msbuild.yaml +6 -0
  150. package/runtime/syntax/nanorc.yaml +16 -0
  151. package/runtime/syntax/nftables.yaml +30 -0
  152. package/runtime/syntax/nginx.yaml +22 -0
  153. package/runtime/syntax/nim.yaml +27 -0
  154. package/runtime/syntax/nix.yaml +32 -0
  155. package/runtime/syntax/nu.yaml +114 -0
  156. package/runtime/syntax/objc.yaml +60 -0
  157. package/runtime/syntax/ocaml.yaml +43 -0
  158. package/runtime/syntax/octave.yaml +83 -0
  159. package/runtime/syntax/odin.yaml +64 -0
  160. package/runtime/syntax/pascal.yaml +45 -0
  161. package/runtime/syntax/patch.yaml +14 -0
  162. package/runtime/syntax/peg.yaml +16 -0
  163. package/runtime/syntax/perl.yaml +58 -0
  164. package/runtime/syntax/php.yaml +60 -0
  165. package/runtime/syntax/pkg-config.yaml +12 -0
  166. package/runtime/syntax/po.yaml +12 -0
  167. package/runtime/syntax/pony.yaml +37 -0
  168. package/runtime/syntax/pov.yaml +21 -0
  169. package/runtime/syntax/privoxy-action.yaml +14 -0
  170. package/runtime/syntax/privoxy-config.yaml +10 -0
  171. package/runtime/syntax/privoxy-filter.yaml +12 -0
  172. package/runtime/syntax/proto.yaml +40 -0
  173. package/runtime/syntax/prql.yaml +84 -0
  174. package/runtime/syntax/puppet.yaml +22 -0
  175. package/runtime/syntax/python2.yaml +60 -0
  176. package/runtime/syntax/python3.yaml +80 -0
  177. package/runtime/syntax/r.yaml +32 -0
  178. package/runtime/syntax/raku.yaml +42 -0
  179. package/runtime/syntax/reST.yaml +18 -0
  180. package/runtime/syntax/renpy.yaml +15 -0
  181. package/runtime/syntax/rpmspec.yaml +43 -0
  182. package/runtime/syntax/ruby.yaml +73 -0
  183. package/runtime/syntax/rust.yaml +78 -0
  184. package/runtime/syntax/sage.yaml +60 -0
  185. package/runtime/syntax/scad.yaml +53 -0
  186. package/runtime/syntax/scala.yaml +33 -0
  187. package/runtime/syntax/sed.yaml +13 -0
  188. package/runtime/syntax/sh.yaml +69 -0
  189. package/runtime/syntax/sls.yaml +15 -0
  190. package/runtime/syntax/smalltalk.yaml +55 -0
  191. package/runtime/syntax/solidity.yaml +41 -0
  192. package/runtime/syntax/sql.yaml +35 -0
  193. package/runtime/syntax/stata.yaml +67 -0
  194. package/runtime/syntax/svelte.yaml +27 -0
  195. package/runtime/syntax/swift.yaml +103 -0
  196. package/runtime/syntax/systemd.yaml +16 -0
  197. package/runtime/syntax/tcl.yaml +18 -0
  198. package/runtime/syntax/terraform.yaml +87 -0
  199. package/runtime/syntax/tex.yaml +32 -0
  200. package/runtime/syntax/toml.yaml +56 -0
  201. package/runtime/syntax/twig.yaml +55 -0
  202. package/runtime/syntax/typescript.yaml +49 -0
  203. package/runtime/syntax/v.yaml +80 -0
  204. package/runtime/syntax/vala.yaml +26 -0
  205. package/runtime/syntax/verilog.yaml +60 -0
  206. package/runtime/syntax/vhdl.yaml +37 -0
  207. package/runtime/syntax/vi.yaml +31 -0
  208. package/runtime/syntax/vue.yaml +64 -0
  209. package/runtime/syntax/xml.yaml +37 -0
  210. package/runtime/syntax/xresources.yaml +14 -0
  211. package/runtime/syntax/yaml.yaml +34 -0
  212. package/runtime/syntax/yum.yaml +12 -0
  213. package/runtime/syntax/zig.yaml +52 -0
  214. package/runtime/syntax/zscript.yaml +72 -0
  215. package/runtime/syntax/zsh.yaml +52 -0
  216. package/single-exe/README.md +80 -0
  217. package/single-exe/assetsHelper.js +90 -0
  218. package/single-exe/assetsLoader.mjs +85 -0
  219. package/single-exe/compiled.js +149 -0
  220. package/single-exe/entry.mjs +9 -0
  221. package/single-exe/packAssets.sh +7 -0
  222. package/src/buffer/backup.js +160 -0
  223. package/src/buffer/buffer.js +126 -0
  224. package/src/buffer/fixed3-codec.js +140 -0
  225. package/src/buffer/loc.js +38 -0
  226. package/src/buffer/message.js +29 -0
  227. package/src/config/clean.js +172 -0
  228. package/src/config/colorscheme.js +99 -0
  229. package/src/config/config.js +122 -0
  230. package/src/config/defaults.js +109 -0
  231. package/src/cui/rpc.mjs +259 -0
  232. package/src/cui/server.mjs +116 -0
  233. package/src/display/ansi-style.js +60 -0
  234. package/src/highlight/highlighter.js +243 -0
  235. package/src/highlight/parser.js +225 -0
  236. package/src/index.js +8009 -0
  237. package/src/lua/engine.js +71 -0
  238. package/src/platform/archive.js +50 -0
  239. package/src/platform/clipboard.js +278 -0
  240. package/src/platform/commands.js +137 -0
  241. package/src/plugins/js-bridge.js +983 -0
  242. package/src/plugins/manager.js +674 -0
  243. package/src/runtime/assets.js +90 -0
  244. package/src/runtime/compiled.js +25 -0
  245. package/src/runtime/encodings.js +10 -0
  246. package/src/runtime/registry.js +134 -0
  247. package/src/screen/cell-buffer.js +81 -0
  248. package/src/screen/events.js +263 -0
  249. package/src/screen/screen.js +142 -0
  250. package/src/screen/vt100.js +571 -0
  251. package/src/shell/shell.js +70 -0
  252. package/testapp.md +55 -0
  253. package/tests/backup.test.js +133 -0
  254. package/tests/cmphex3b64.js +95 -0
  255. package/tests/pty-demo.js +492 -0
  256. package/tests/wv-client.js +96 -0
  257. package/tui +5 -0
  258. package/wui +5 -0
@@ -0,0 +1,160 @@
1
+ // Crash recovery backup system compatible with Go micro's internal/buffer/backup.go.
2
+
3
+ import { join } from "node:path";
4
+ import { existsSync, statSync, unlinkSync } from "node:fs";
5
+ import { mkdir, writeFile, readFile, rename } from "node:fs/promises";
6
+ import { homedir } from "node:os";
7
+ import { createHash } from "node:crypto";
8
+
9
+ export const BACKUP_SUFFIX = ".micro-backup";
10
+
11
+ export function getBackupDir(buf, configDir) {
12
+ const raw = String(buf?.Settings?.backupdir ?? "");
13
+ if (!raw) return join(configDir, "backups");
14
+ if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
15
+ return raw.replace(/^~/, homedir());
16
+ }
17
+ // Node has no portable equivalent of Go's user.Lookup for ~otheruser.
18
+ if (raw.startsWith("~")) return join(configDir, "backups");
19
+ return raw;
20
+ }
21
+
22
+ function queryEscapePath(path) {
23
+ return encodeURIComponent(String(path).replaceAll("\\", "/"))
24
+ .replace(/[!'()*]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`)
25
+ .replace(/%20/g, "+");
26
+ } // '
27
+
28
+ function legacyEscapePath(path) {
29
+ let escaped = String(path).replaceAll("\\", "/");
30
+ if (process.platform === "win32") escaped = escaped.replaceAll(":", "%");
31
+ return escaped.replaceAll("/", "%");
32
+ }
33
+
34
+ export function determineBackupPath(backupDirPath, absPath) {
35
+ const urlName = join(backupDirPath, queryEscapePath(absPath));
36
+ if (existsSync(urlName)) return { name: urlName, resolveName: null };
37
+
38
+ const legacyName = join(backupDirPath, legacyEscapePath(absPath));
39
+ if (existsSync(legacyName)) return { name: legacyName, resolveName: null };
40
+
41
+ if (Buffer.byteLength(urlName + BACKUP_SUFFIX) > 255) {
42
+ const hash = createHash("md5").update(absPath).digest("hex");
43
+ return {
44
+ name: join(backupDirPath, hash),
45
+ resolveName: join(backupDirPath, hash + ".path"),
46
+ };
47
+ }
48
+ return { name: urlName, resolveName: null };
49
+ }
50
+
51
+ export async function writeBackup(buf, configDir, path = buf?.AbsPath ?? buf?.path, { force = false } = {}) {
52
+ if ((!force && !buf?.Settings?.backup) || !path || buf.type !== "default") return false;
53
+ const dir = getBackupDir(buf, configDir);
54
+ await mkdir(dir, { recursive: true });
55
+ const { name, resolveName } = determineBackupPath(dir, path);
56
+ const tmp = name + BACKUP_SUFFIX;
57
+ try {
58
+ await writeFile(tmp, buf.lines.join("\n"), "utf8");
59
+ await rename(tmp, name);
60
+ if (resolveName) await writeFile(resolveName, path, "utf8");
61
+ return true;
62
+ } catch (error) {
63
+ try { unlinkSync(tmp); } catch {}
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ export function removeBackup(buf, configDir, path = buf?.AbsPath ?? buf?.path) {
69
+ if (buf?.Settings?.permbackup || buf?._forceKeepBackup) return;
70
+ if (!path || buf.type !== "default") return;
71
+ const dir = getBackupDir(buf, configDir);
72
+ const { name, resolveName } = determineBackupPath(dir, path);
73
+ try { unlinkSync(name); } catch {}
74
+ if (resolveName) try { unlinkSync(resolveName); } catch {}
75
+ }
76
+
77
+ // promptFn(msg) -> Promise<string>
78
+ // Returns { recovered: bool, abort: bool }
79
+ export async function applyBackup(buf, configDir, promptFn) {
80
+ if (!buf?.Settings?.backup || buf?.Settings?.permbackup) return { recovered: false, abort: false };
81
+ if (!buf.path || buf.type !== "default") return { recovered: false, abort: false };
82
+
83
+ const dir = getBackupDir(buf, configDir);
84
+ const { name: backupFile, resolveName } = determineBackupPath(dir, buf.AbsPath ?? buf.path);
85
+ if (!existsSync(backupFile)) return { recovered: false, abort: false };
86
+
87
+ let info;
88
+ try { info = statSync(backupFile); } catch { return { recovered: false, abort: false }; }
89
+
90
+ const t = info.mtime;
91
+ const dateStr =
92
+ t.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" }) +
93
+ " at " +
94
+ t.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }) +
95
+ ", " + t.getFullYear();
96
+
97
+ const ticcc='```';
98
+
99
+ const msg = `
100
+ # Backup detected! ⚠️
101
+ - File path/url:
102
+ ${ticcc}
103
+ ${buf.path}
104
+ ${ticcc}
105
+ - either micro crashed
106
+ - or another micro is running
107
+ - or an error occurred while saving
108
+ - The file may be corrupted
109
+ # Date of backup 🕰
110
+ - ${dateStr}
111
+ # Path of backup
112
+ ${ticcc}
113
+ ${backupFile}
114
+ ${ticcc}
115
+ # Recovery options
116
+ ## r = 'recover'
117
+ - Apply the backup
118
+ - as unsaved changes to the current buffer
119
+ - When the buffer is closed, the backup will be removed.
120
+ ## i = 'ignore'
121
+ - Ignore & remove the backup
122
+ ## a = 'abort'
123
+ - Abort the open operation
124
+ - Open an empty buffer
125
+ - Keep the backup
126
+ ### Your choice
127
+ - [r]ecover, [i]gnore, [a]bort
128
+ `;
129
+
130
+ const options = ["r", "i", "a", "recover", "ignore", "abort"];
131
+ let choice = -1;
132
+ let prompt = msg;
133
+ while (choice === -1) {
134
+ const resp = await promptFn(prompt);
135
+ const idx = options.indexOf(resp.trim().toLowerCase());
136
+ if (idx !== -1) choice = idx % 3;
137
+ else prompt = "\n#### Invalid choice!";
138
+ }
139
+
140
+ if (choice === 0) {
141
+ try {
142
+ const text = await readFile(backupFile, "utf8");
143
+ buf.lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
144
+ if (!buf.lines.length) buf.lines = [""];
145
+ buf._recovered = true;
146
+ buf._savedSerial = -1;
147
+ buf.setModified?.(true);
148
+ if (!buf.setModified) buf.modified = true;
149
+ return { recovered: true, abort: false };
150
+ } catch {
151
+ return { recovered: false, abort: false };
152
+ }
153
+ }
154
+ if (choice === 1) {
155
+ try { unlinkSync(backupFile); } catch {}
156
+ if (resolveName) try { unlinkSync(resolveName); } catch {}
157
+ return { recovered: false, abort: false };
158
+ }
159
+ return { recovered: false, abort: true };
160
+ }
@@ -0,0 +1,126 @@
1
+ import { basename } from "node:path";
2
+ import { Loc } from "./loc.js";
3
+
4
+ export const BTDefault = { Kind: 0, Scratch: false, Readonly: false };
5
+ export const BTHelp = { Kind: 1, Scratch: true, Readonly: true };
6
+ export const BTLog = { Kind: 2, Scratch: true, Readonly: true };
7
+ export const BTScratch = { Kind: 3, Scratch: true, Readonly: false };
8
+ export const BTRaw = { Kind: 4, Scratch: true, Readonly: false };
9
+ export const BTInfo = { Kind: 5, Scratch: true, Readonly: true };
10
+
11
+ export class BufferCore {
12
+ constructor({ text = "", path = "", type = BTDefault, settings = {} } = {}) {
13
+ this.Path = path;
14
+ this.AbsPath = path;
15
+ this.Name = path ? basename(path) : "No name";
16
+ this.Type = type;
17
+ this.Settings = { filetype: "unknown", tabstospaces: false, tabsize: 4, ...settings };
18
+ this.lines = normalizeText(text).split("\n");
19
+ if (this.lines.length === 0) this.lines = [""];
20
+ this.Messages = [];
21
+ this.diffBase = "";
22
+ this.modified = false;
23
+ }
24
+
25
+ Line(n) {
26
+ return this.lines[n] ?? "";
27
+ }
28
+
29
+ LineBytes(n) {
30
+ return new TextEncoder().encode(this.Line(n));
31
+ }
32
+
33
+ LinesNum() {
34
+ return this.lines.length;
35
+ }
36
+
37
+ Size() {
38
+ return new TextEncoder().encode(this.Bytes()).byteLength;
39
+ }
40
+
41
+ Bytes() {
42
+ return this.lines.join("\n");
43
+ }
44
+
45
+ FileType() {
46
+ return this.Settings.filetype ?? "unknown";
47
+ }
48
+
49
+ SetOption(option, value) {
50
+ this.Settings[option] = parseOption(value);
51
+ }
52
+
53
+ DoSetOptionNative(option, value) {
54
+ this.Settings[option] = value;
55
+ }
56
+
57
+ Insert(loc, text) {
58
+ const pos = normalizeLoc(loc);
59
+ const line = this.Line(pos.Y);
60
+ const before = line.slice(0, pos.X);
61
+ const after = line.slice(pos.X);
62
+ const parts = normalizeText(text).split("\n");
63
+ if (parts.length === 1) {
64
+ this.lines[pos.Y] = before + parts[0] + after;
65
+ } else {
66
+ const replacement = [before + parts[0], ...parts.slice(1, -1), parts.at(-1) + after];
67
+ this.lines.splice(pos.Y, 1, ...replacement);
68
+ }
69
+ this.modified = true;
70
+ }
71
+
72
+ Replace(start, end, text) {
73
+ const s = normalizeLoc(start);
74
+ const e = normalizeLoc(end);
75
+ if (s.Y === e.Y) {
76
+ const line = this.Line(s.Y);
77
+ this.lines[s.Y] = line.slice(0, s.X) + text + line.slice(e.X);
78
+ } else {
79
+ const first = this.Line(s.Y).slice(0, s.X);
80
+ const last = this.Line(e.Y).slice(e.X);
81
+ const parts = normalizeText(text).split("\n");
82
+ const replacement = parts.length === 1 ? [first + parts[0] + last] : [first + parts[0], ...parts.slice(1, -1), parts.at(-1) + last];
83
+ this.lines.splice(s.Y, e.Y - s.Y + 1, ...replacement);
84
+ }
85
+ this.modified = true;
86
+ }
87
+
88
+ SetDiffBase(text) {
89
+ this.diffBase = String(text);
90
+ }
91
+
92
+ AddMessage(message) {
93
+ this.Messages.push(message);
94
+ }
95
+
96
+ ClearMessages(owner) {
97
+ this.Messages = this.Messages.filter((message) => message.Owner !== owner);
98
+ }
99
+
100
+ ClearAllMessages() {
101
+ this.Messages = [];
102
+ }
103
+ }
104
+
105
+ export function byteOffset(pos, buffer) {
106
+ const loc = normalizeLoc(pos);
107
+ let offset = 0;
108
+ for (let y = 0; y < loc.Y; y++) offset += buffer.Line(y).length + 1;
109
+ return offset + buffer.Line(loc.Y).slice(0, loc.X).length;
110
+ }
111
+
112
+ function normalizeLoc(value) {
113
+ if (value instanceof Loc) return value;
114
+ return new Loc(value?.X ?? value?.x ?? 0, value?.Y ?? value?.y ?? 0);
115
+ }
116
+
117
+ function normalizeText(text) {
118
+ return String(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
119
+ }
120
+
121
+ function parseOption(value) {
122
+ if (value === "on" || value === "true") return true;
123
+ if (value === "off" || value === "false") return false;
124
+ if (/^-?\d+(\.\d+)?$/.test(String(value))) return Number(value);
125
+ return value;
126
+ }
@@ -0,0 +1,140 @@
1
+ const HEX = Array.from({ length: 256 }, (_, i) =>
2
+ i.toString(16).padStart(2, "0"),
3
+ );
4
+ const ENCODE_TABLE = new Uint8Array(256 * 3);
5
+ const DECODE_HEX = new Int16Array(128);
6
+
7
+ DECODE_HEX.fill(-1);
8
+
9
+ for (let i = 0; i < 10; i++) {
10
+ DECODE_HEX[0x30 + i] = i;
11
+ }
12
+ for (let i = 0; i < 6; i++) {
13
+ DECODE_HEX[0x41 + i] = 10 + i;
14
+ DECODE_HEX[0x61 + i] = 10 + i;
15
+ }
16
+
17
+ for (let byte = 0; byte < 256; byte++) {
18
+ const offset = byte * 3;
19
+
20
+ if (byte >= 0x20 && byte <= 0x7e) {
21
+ ENCODE_TABLE[offset] = byte;
22
+ ENCODE_TABLE[offset + 1] = 0x2e;
23
+ ENCODE_TABLE[offset + 2] = 0x2e;
24
+ } else {
25
+ ENCODE_TABLE[offset] = 0x5c;
26
+ ENCODE_TABLE[offset + 1] = HEX[byte].charCodeAt(0);
27
+ ENCODE_TABLE[offset + 2] = HEX[byte].charCodeAt(1);
28
+ }
29
+ }
30
+
31
+ export class Fixed3DecodeError extends SyntaxError {
32
+ constructor(message, position) {
33
+ super(`${message} at text offset ${position}`);
34
+ this.name = "Fixed3DecodeError";
35
+ this.position = position;
36
+ this.byteOffset = Math.floor(position / 3);
37
+ }
38
+ }
39
+
40
+ export function encodeBinary(input) {
41
+ return encodeBinaryToBuffer(input).toString("latin1");
42
+ }
43
+
44
+ export function encodeBinaryToBuffer(input) {
45
+ const bytes = input instanceof Uint8Array ? input : Buffer.from(input);
46
+ const out = Buffer.allocUnsafe(bytes.byteLength * 3);
47
+ let j = 0;
48
+
49
+ for (let i = 0; i < bytes.byteLength; i++) {
50
+ const offset = bytes[i] * 3;
51
+ out[j++] = ENCODE_TABLE[offset];
52
+ out[j++] = ENCODE_TABLE[offset + 1];
53
+ out[j++] = ENCODE_TABLE[offset + 2];
54
+ }
55
+
56
+ return out;
57
+ }
58
+
59
+ export function decodeBinary(text) {
60
+ if (typeof text !== "string") {
61
+ throw new TypeError("decodeBinary() expects a string");
62
+ }
63
+
64
+ return decodeBinaryBytes(Buffer.from(text, "latin1"));
65
+ }
66
+
67
+ export function decodeBinaryBytes(input) {
68
+ const bytes = input instanceof Uint8Array ? input : Buffer.from(input);
69
+ const out = Buffer.allocUnsafe(Math.floor(bytes.byteLength / 3));
70
+ let j = 0;
71
+
72
+ for (let i = 0; i < bytes.byteLength; i += 3) {
73
+ const a = bytes[i];
74
+ const b = bytes[i + 1];
75
+ const c = bytes[i + 2];
76
+
77
+ if (b === 0x2e && c === 0x2e) {
78
+ out[j++] = a;
79
+ } else {
80
+ out[j++] = (DECODE_HEX[b] << 4) | DECODE_HEX[c];
81
+ }
82
+ }
83
+
84
+ return out;
85
+ }
86
+
87
+ export function decodeBinaryStrict(text) {
88
+ if (typeof text !== "string") {
89
+ throw new TypeError("decodeBinary() expects a string");
90
+ }
91
+
92
+ if (text.length % 3 !== 0) {
93
+ throw new Fixed3DecodeError("input length is not a multiple of 3", text.length);
94
+ }
95
+
96
+ const out = Buffer.allocUnsafe(text.length / 3);
97
+ let j = 0;
98
+
99
+ for (let i = 0; i < text.length; i += 3) {
100
+ const a = text.charCodeAt(i);
101
+ const b = text.charCodeAt(i + 1);
102
+ const c = text.charCodeAt(i + 2);
103
+
104
+ if (b === 0x2e && c === 0x2e) {
105
+ if (a < 0x20 || a > 0x7e) {
106
+ throw new Fixed3DecodeError("printable cell has non-printable byte", i);
107
+ }
108
+ out[j++] = a;
109
+ continue;
110
+ }
111
+
112
+ if (a !== 0x5c) {
113
+ throw new Fixed3DecodeError("escaped cell must start with backslash", i);
114
+ }
115
+
116
+ const hi = b < 128 ? DECODE_HEX[b] : -1;
117
+ const lo = c < 128 ? DECODE_HEX[c] : -1;
118
+ if (hi < 0 || lo < 0) {
119
+ throw new Fixed3DecodeError("escaped cell must contain two hex digits", i);
120
+ }
121
+
122
+ const byte = (hi << 4) | lo;
123
+ if (byte >= 0x20 && byte <= 0x7e) {
124
+ throw new Fixed3DecodeError("printable byte must use c.. form", i);
125
+ }
126
+
127
+ out[j++] = byte;
128
+ }
129
+
130
+ return out;
131
+ }
132
+
133
+ export default {
134
+ encodeBinary,
135
+ encodeBinaryToBuffer,
136
+ decodeBinary,
137
+ decodeBinaryBytes,
138
+ decodeBinaryStrict,
139
+ Fixed3DecodeError,
140
+ };
@@ -0,0 +1,38 @@
1
+ export class Loc {
2
+ constructor(x = 0, y = 0) {
3
+ this.X = x;
4
+ this.Y = y;
5
+ this.x = x;
6
+ this.y = y;
7
+ }
8
+
9
+ lessThan(other) {
10
+ return this.Y < other.Y || (this.Y === other.Y && this.X < other.X);
11
+ }
12
+
13
+ greaterThan(other) {
14
+ return this.Y > other.Y || (this.Y === other.Y && this.X > other.X);
15
+ }
16
+
17
+ greaterEqual(other) {
18
+ return this.greaterThan(other) || (this.X === other.X && this.Y === other.Y);
19
+ }
20
+
21
+ lessEqual(other) {
22
+ return this.lessThan(other) || (this.X === other.X && this.Y === other.Y);
23
+ }
24
+
25
+ clamp(start, end) {
26
+ if (this.greaterEqual(end)) return end;
27
+ if (this.lessThan(start)) return start;
28
+ return this;
29
+ }
30
+
31
+ toJSON() {
32
+ return { X: this.X, Y: this.Y };
33
+ }
34
+ }
35
+
36
+ export function loc(x = 0, y = 0) {
37
+ return new Loc(x, y);
38
+ }
@@ -0,0 +1,29 @@
1
+ import { Loc } from "./loc.js";
2
+
3
+ export const MTInfo = 0;
4
+ export const MTWarning = 1;
5
+ export const MTError = 2;
6
+
7
+ export class BufferMessage {
8
+ constructor(owner, msg, start, end, kind) {
9
+ this.Owner = owner;
10
+ this.Msg = msg;
11
+ this.Start = start;
12
+ this.End = end;
13
+ this.Kind = kind;
14
+ }
15
+ }
16
+
17
+ export function newMessage(owner, msg, start, end, kind) {
18
+ return new BufferMessage(owner, msg, normalizeLoc(start), normalizeLoc(end), kind);
19
+ }
20
+
21
+ export function newMessageAtLine(owner, msg, line, kind) {
22
+ const pos = new Loc(-1, Number(line) - 1);
23
+ return newMessage(owner, msg, pos, pos, kind);
24
+ }
25
+
26
+ function normalizeLoc(value) {
27
+ if (value instanceof Loc) return value;
28
+ return new Loc(value?.X ?? value?.x ?? 0, value?.Y ?? value?.y ?? 0);
29
+ }
@@ -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
+ }