bunmicro 0.8.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 (237) hide show
  1. package/LICENSE +22 -0
  2. package/PORTING.md +34 -0
  3. package/README.md +153 -0
  4. package/bmi +5 -0
  5. package/bun.lock +17 -0
  6. package/bunmicro +5 -0
  7. package/hlw.md +5 -0
  8. package/package.json +18 -0
  9. package/runtime/colorschemes/atom-dark.micro +33 -0
  10. package/runtime/colorschemes/bubblegum.micro +31 -0
  11. package/runtime/colorschemes/cmc-16.micro +47 -0
  12. package/runtime/colorschemes/cmc-tc.micro +43 -0
  13. package/runtime/colorschemes/darcula.micro +34 -0
  14. package/runtime/colorschemes/default.micro +1 -0
  15. package/runtime/colorschemes/dracula-tc.micro +49 -0
  16. package/runtime/colorschemes/dukedark-tc.micro +38 -0
  17. package/runtime/colorschemes/dukelight-tc.micro +38 -0
  18. package/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
  19. package/runtime/colorschemes/geany.micro +29 -0
  20. package/runtime/colorschemes/gotham.micro +29 -0
  21. package/runtime/colorschemes/gruvbox-tc.micro +29 -0
  22. package/runtime/colorschemes/gruvbox.micro +26 -0
  23. package/runtime/colorschemes/material-tc.micro +36 -0
  24. package/runtime/colorschemes/monokai-dark.micro +28 -0
  25. package/runtime/colorschemes/monokai.micro +34 -0
  26. package/runtime/colorschemes/one-dark.micro +39 -0
  27. package/runtime/colorschemes/railscast.micro +37 -0
  28. package/runtime/colorschemes/simple.micro +33 -0
  29. package/runtime/colorschemes/solarized-tc.micro +31 -0
  30. package/runtime/colorschemes/solarized.micro +30 -0
  31. package/runtime/colorschemes/sunny-day.micro +29 -0
  32. package/runtime/colorschemes/twilight.micro +40 -0
  33. package/runtime/colorschemes/zenburn.micro +30 -0
  34. package/runtime/help/actions.md +161 -0
  35. package/runtime/help/colors.md +421 -0
  36. package/runtime/help/commands.md +161 -0
  37. package/runtime/help/copypaste.md +149 -0
  38. package/runtime/help/defaultkeys.md +141 -0
  39. package/runtime/help/help.md +63 -0
  40. package/runtime/help/keybindings.md +760 -0
  41. package/runtime/help/linter.md +90 -0
  42. package/runtime/help/options.md +701 -0
  43. package/runtime/help/plugins.md +544 -0
  44. package/runtime/help/tutorial.md +112 -0
  45. package/runtime/jsplugins/chapter/chapter.js +108 -0
  46. package/runtime/jsplugins/diff/diff.js +46 -0
  47. package/runtime/jsplugins/example/example.js +108 -0
  48. package/runtime/jsplugins/linter/linter.js +281 -0
  49. package/runtime/plugins/autoclose/autoclose.lua +75 -0
  50. package/runtime/plugins/ftoptions/ftoptions.lua +17 -0
  51. package/runtime/plugins/literate/README.md +5 -0
  52. package/runtime/plugins/literate/literate.lua +55 -0
  53. package/runtime/plugins/status/help/status.md +21 -0
  54. package/runtime/plugins/status/status.lua +62 -0
  55. package/runtime/syntax/LICENSE +22 -0
  56. package/runtime/syntax/PowerShell.yaml +128 -0
  57. package/runtime/syntax/README.md +63 -0
  58. package/runtime/syntax/ada.yaml +43 -0
  59. package/runtime/syntax/apacheconf.yaml +59 -0
  60. package/runtime/syntax/arduino.yaml +101 -0
  61. package/runtime/syntax/asciidoc.yaml +51 -0
  62. package/runtime/syntax/asm.yaml +123 -0
  63. package/runtime/syntax/ats.yaml +99 -0
  64. package/runtime/syntax/awk.yaml +44 -0
  65. package/runtime/syntax/b.yaml +87 -0
  66. package/runtime/syntax/bat.yaml +57 -0
  67. package/runtime/syntax/c.yaml +60 -0
  68. package/runtime/syntax/caddyfile.yaml +23 -0
  69. package/runtime/syntax/cake.yaml +7 -0
  70. package/runtime/syntax/clojure.yaml +38 -0
  71. package/runtime/syntax/cmake.yaml +42 -0
  72. package/runtime/syntax/coffeescript.yaml +56 -0
  73. package/runtime/syntax/colortest.yaml +19 -0
  74. package/runtime/syntax/conky.yaml +17 -0
  75. package/runtime/syntax/cpp.yaml +91 -0
  76. package/runtime/syntax/crontab.yaml +36 -0
  77. package/runtime/syntax/crystal.yaml +72 -0
  78. package/runtime/syntax/csharp.yaml +52 -0
  79. package/runtime/syntax/css.yaml +44 -0
  80. package/runtime/syntax/csx.yaml +8 -0
  81. package/runtime/syntax/cuda.yaml +68 -0
  82. package/runtime/syntax/cython.yaml +52 -0
  83. package/runtime/syntax/d.yaml +121 -0
  84. package/runtime/syntax/dart.yaml +46 -0
  85. package/runtime/syntax/default.yaml +10 -0
  86. package/runtime/syntax/dockerfile.yaml +36 -0
  87. package/runtime/syntax/dot.yaml +29 -0
  88. package/runtime/syntax/elixir.yaml +30 -0
  89. package/runtime/syntax/elm.yaml +38 -0
  90. package/runtime/syntax/erb.yaml +42 -0
  91. package/runtime/syntax/erlang.yaml +45 -0
  92. package/runtime/syntax/fish.yaml +48 -0
  93. package/runtime/syntax/forth.yaml +34 -0
  94. package/runtime/syntax/fortran.yaml +64 -0
  95. package/runtime/syntax/freebsd-kernel.yaml +14 -0
  96. package/runtime/syntax/fsharp.yaml +48 -0
  97. package/runtime/syntax/gdscript.yaml +61 -0
  98. package/runtime/syntax/gemini.yaml +19 -0
  99. package/runtime/syntax/gentoo-ebuild.yaml +48 -0
  100. package/runtime/syntax/gentoo-etc-portage.yaml +23 -0
  101. package/runtime/syntax/git-commit.yaml +35 -0
  102. package/runtime/syntax/git-config.yaml +14 -0
  103. package/runtime/syntax/git-rebase-todo.yaml +19 -0
  104. package/runtime/syntax/gleam.yaml +69 -0
  105. package/runtime/syntax/glsl.yaml +26 -0
  106. package/runtime/syntax/gnuplot.yaml +15 -0
  107. package/runtime/syntax/go.yaml +62 -0
  108. package/runtime/syntax/godoc.yaml +17 -0
  109. package/runtime/syntax/golo.yaml +73 -0
  110. package/runtime/syntax/gomod.yaml +31 -0
  111. package/runtime/syntax/graphql.yaml +47 -0
  112. package/runtime/syntax/groff.yaml +30 -0
  113. package/runtime/syntax/groovy.yaml +111 -0
  114. package/runtime/syntax/haml.yaml +16 -0
  115. package/runtime/syntax/hare.yaml +52 -0
  116. package/runtime/syntax/haskell.yaml +52 -0
  117. package/runtime/syntax/hc.yaml +52 -0
  118. package/runtime/syntax/html.yaml +70 -0
  119. package/runtime/syntax/html4.yaml +25 -0
  120. package/runtime/syntax/html5.yaml +25 -0
  121. package/runtime/syntax/ini.yaml +23 -0
  122. package/runtime/syntax/inputrc.yaml +14 -0
  123. package/runtime/syntax/java.yaml +37 -0
  124. package/runtime/syntax/javascript.yaml +76 -0
  125. package/runtime/syntax/jinja2.yaml +19 -0
  126. package/runtime/syntax/json.yaml +39 -0
  127. package/runtime/syntax/jsonnet.yaml +92 -0
  128. package/runtime/syntax/julia.yaml +57 -0
  129. package/runtime/syntax/justfile.yaml +40 -0
  130. package/runtime/syntax/keymap.yaml +27 -0
  131. package/runtime/syntax/kickstart.yaml +16 -0
  132. package/runtime/syntax/kotlin.yaml +66 -0
  133. package/runtime/syntax/kvlang.yaml +67 -0
  134. package/runtime/syntax/ledger.yaml +14 -0
  135. package/runtime/syntax/lfe.yaml +17 -0
  136. package/runtime/syntax/lilypond.yaml +26 -0
  137. package/runtime/syntax/lisp.yaml +17 -0
  138. package/runtime/syntax/log.yaml +92 -0
  139. package/runtime/syntax/lua.yaml +111 -0
  140. package/runtime/syntax/mail.yaml +25 -0
  141. package/runtime/syntax/makefile.yaml +38 -0
  142. package/runtime/syntax/man.yaml +12 -0
  143. package/runtime/syntax/markdown.yaml +49 -0
  144. package/runtime/syntax/mc.yaml +23 -0
  145. package/runtime/syntax/meson.yaml +51 -0
  146. package/runtime/syntax/micro.yaml +34 -0
  147. package/runtime/syntax/mpdconf.yaml +13 -0
  148. package/runtime/syntax/msbuild.yaml +6 -0
  149. package/runtime/syntax/nanorc.yaml +16 -0
  150. package/runtime/syntax/nftables.yaml +30 -0
  151. package/runtime/syntax/nginx.yaml +22 -0
  152. package/runtime/syntax/nim.yaml +27 -0
  153. package/runtime/syntax/nix.yaml +32 -0
  154. package/runtime/syntax/nu.yaml +114 -0
  155. package/runtime/syntax/objc.yaml +60 -0
  156. package/runtime/syntax/ocaml.yaml +43 -0
  157. package/runtime/syntax/octave.yaml +83 -0
  158. package/runtime/syntax/odin.yaml +64 -0
  159. package/runtime/syntax/pascal.yaml +45 -0
  160. package/runtime/syntax/patch.yaml +14 -0
  161. package/runtime/syntax/peg.yaml +16 -0
  162. package/runtime/syntax/perl.yaml +58 -0
  163. package/runtime/syntax/php.yaml +60 -0
  164. package/runtime/syntax/pkg-config.yaml +12 -0
  165. package/runtime/syntax/po.yaml +12 -0
  166. package/runtime/syntax/pony.yaml +37 -0
  167. package/runtime/syntax/pov.yaml +21 -0
  168. package/runtime/syntax/privoxy-action.yaml +14 -0
  169. package/runtime/syntax/privoxy-config.yaml +10 -0
  170. package/runtime/syntax/privoxy-filter.yaml +12 -0
  171. package/runtime/syntax/proto.yaml +40 -0
  172. package/runtime/syntax/prql.yaml +84 -0
  173. package/runtime/syntax/puppet.yaml +22 -0
  174. package/runtime/syntax/python2.yaml +60 -0
  175. package/runtime/syntax/python3.yaml +62 -0
  176. package/runtime/syntax/r.yaml +32 -0
  177. package/runtime/syntax/raku.yaml +42 -0
  178. package/runtime/syntax/reST.yaml +18 -0
  179. package/runtime/syntax/renpy.yaml +15 -0
  180. package/runtime/syntax/rpmspec.yaml +43 -0
  181. package/runtime/syntax/ruby.yaml +73 -0
  182. package/runtime/syntax/rust.yaml +78 -0
  183. package/runtime/syntax/sage.yaml +60 -0
  184. package/runtime/syntax/scad.yaml +53 -0
  185. package/runtime/syntax/scala.yaml +33 -0
  186. package/runtime/syntax/sed.yaml +13 -0
  187. package/runtime/syntax/sh.yaml +69 -0
  188. package/runtime/syntax/sls.yaml +15 -0
  189. package/runtime/syntax/smalltalk.yaml +55 -0
  190. package/runtime/syntax/solidity.yaml +41 -0
  191. package/runtime/syntax/sql.yaml +35 -0
  192. package/runtime/syntax/stata.yaml +67 -0
  193. package/runtime/syntax/svelte.yaml +27 -0
  194. package/runtime/syntax/swift.yaml +103 -0
  195. package/runtime/syntax/systemd.yaml +16 -0
  196. package/runtime/syntax/tcl.yaml +18 -0
  197. package/runtime/syntax/terraform.yaml +87 -0
  198. package/runtime/syntax/tex.yaml +32 -0
  199. package/runtime/syntax/toml.yaml +56 -0
  200. package/runtime/syntax/twig.yaml +55 -0
  201. package/runtime/syntax/typescript.yaml +49 -0
  202. package/runtime/syntax/v.yaml +80 -0
  203. package/runtime/syntax/vala.yaml +26 -0
  204. package/runtime/syntax/verilog.yaml +60 -0
  205. package/runtime/syntax/vhdl.yaml +37 -0
  206. package/runtime/syntax/vi.yaml +31 -0
  207. package/runtime/syntax/vue.yaml +64 -0
  208. package/runtime/syntax/xml.yaml +37 -0
  209. package/runtime/syntax/xresources.yaml +14 -0
  210. package/runtime/syntax/yaml.yaml +34 -0
  211. package/runtime/syntax/yum.yaml +12 -0
  212. package/runtime/syntax/zig.yaml +52 -0
  213. package/runtime/syntax/zscript.yaml +72 -0
  214. package/runtime/syntax/zsh.yaml +52 -0
  215. package/src/buffer/buffer.js +126 -0
  216. package/src/buffer/loc.js +38 -0
  217. package/src/buffer/message.js +29 -0
  218. package/src/config/colorscheme.js +109 -0
  219. package/src/config/config.js +118 -0
  220. package/src/config/defaults.js +102 -0
  221. package/src/display/ansi-style.js +60 -0
  222. package/src/highlight/highlighter.js +237 -0
  223. package/src/highlight/parser.js +137 -0
  224. package/src/index.js +5942 -0
  225. package/src/lua/engine.js +38 -0
  226. package/src/platform/archive.js +50 -0
  227. package/src/platform/clipboard.js +160 -0
  228. package/src/platform/commands.js +140 -0
  229. package/src/plugins/js-bridge.js +902 -0
  230. package/src/plugins/manager.js +619 -0
  231. package/src/runtime/registry.js +89 -0
  232. package/src/screen/cell-buffer.js +81 -0
  233. package/src/screen/events.js +263 -0
  234. package/src/screen/screen.js +118 -0
  235. package/src/screen/vt100.js +391 -0
  236. package/src/shell/shell.js +70 -0
  237. package/todo.txt +359 -0
@@ -0,0 +1,118 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ //import { Glob } from "bun";
6
+ import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./defaults.js";
7
+
8
+ export class Config {
9
+ constructor({ configDir = "" } = {}) {
10
+ this.configDir = configDir || defaultConfigDir();
11
+ this.globalSettings = defaultAllSettings();
12
+ this.volatileSettings = new Set();
13
+ this.modifiedSettings = new Set();
14
+ this.parsedSettings = {};
15
+ }
16
+
17
+ async init() {
18
+ await mkdir(this.configDir, { recursive: true });
19
+ await this.readSettings();
20
+ return this;
21
+ }
22
+
23
+ async readSettings() {
24
+ const path = join(this.configDir, "settings.json");
25
+ if (!existsSync(path)) return;
26
+ const text = await readFile(path, "utf8");
27
+ if (text.trimStart().startsWith("null") || text.trim() === "") return;
28
+ this.parsedSettings = Bun.JSON5.parse(text);
29
+ this.applyParsedSettings(this.parsedSettings);
30
+ }
31
+
32
+ applyCliSettings(settings) {
33
+ for (const [key, raw] of settings) {
34
+ if (!(key in this.globalSettings)) continue;
35
+ this.setGlobalOptionNative(key, parseSetting(raw), { volatile: true });
36
+ }
37
+ }
38
+
39
+ applyParsedSettings(parsed) {
40
+ for (const [key, value] of Object.entries(parsed)) {
41
+ if (key.startsWith("ft:")) continue;
42
+ if (key.startsWith("glob:")) {
43
+ //new Glob(key.slice(5));
44
+ continue;
45
+ }
46
+ if (!(key in this.globalSettings)) continue;
47
+ this.setGlobalOptionNative(key, normalizeSetting(key, value), { modified: false });
48
+ }
49
+ }
50
+
51
+ registerCommonOption(plugin, option, value) {
52
+ this.registerGlobalOption(`${plugin}.${option}`, value);
53
+ }
54
+
55
+ registerGlobalOption(option, value) {
56
+ if (!(option in this.globalSettings)) this.globalSettings[option] = value;
57
+ if (Object.hasOwn(this.parsedSettings, option)) {
58
+ this.setGlobalOptionNative(option, normalizeSetting(option, this.parsedSettings[option]), { modified: false });
59
+ }
60
+ }
61
+
62
+ getGlobalOption(option) {
63
+ return this.globalSettings[option];
64
+ }
65
+
66
+ setGlobalOptionNative(option, value, { volatile = false, modified = true } = {}) {
67
+ if (!(option in this.globalSettings)) throw new Error(`Invalid option: ${option}`);
68
+ validateOption(option, value);
69
+ this.globalSettings[option] = value;
70
+ if (volatile) this.volatileSettings.add(option);
71
+ if (modified) this.modifiedSettings.add(option);
72
+ }
73
+
74
+ async saveSettings() {
75
+ const path = join(this.configDir, "settings.json");
76
+ const out = { ...this.parsedSettings };
77
+ for (const key of this.modifiedSettings) {
78
+ if (!this.volatileSettings.has(key) && !LOCAL_SETTINGS.has(key)) out[key] = this.globalSettings[key];
79
+ }
80
+ // Remove any local-only settings that may have leaked into parsedSettings previously.
81
+ for (const key of LOCAL_SETTINGS) delete out[key];
82
+ await Bun.write(path, JSON.stringify(out, null, " ") + "\n");
83
+ this.parsedSettings = { ...out };
84
+ }
85
+ }
86
+
87
+ export function defaultConfigDir() {
88
+ if (process.env.MICRO_CONFIG_HOME) return process.env.MICRO_CONFIG_HOME;
89
+ const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
90
+ return join(xdg, "micro");
91
+ }
92
+
93
+ export function parseSetting(value) {
94
+ if (value === "true" || value === "on") return true;
95
+ if (value === "false" || value === "off") return false;
96
+ if (/^-?\d+(\.\d+)?$/.test(value)) return Number(value);
97
+ return value;
98
+ }
99
+
100
+ function normalizeSetting(key, value) {
101
+ if (key === "autosave" && typeof value === "boolean") return value ? 8 : 0;
102
+ return value;
103
+ }
104
+
105
+ function validateOption(option, value) {
106
+ if (option === "encoding") {
107
+ try { new TextDecoder(String(value || "utf-8")); }
108
+ catch { throw new Error(`Invalid encoding: ${value}`); }
109
+ }
110
+ const choices = OPTION_CHOICES[option];
111
+ if (choices && !choices.includes(value)) {
112
+ throw new Error(`Invalid value for ${option}: ${value}`);
113
+ }
114
+ if (["autosave", "colorcolumn", "detectlimit", "pageoverlap", "scrollmargin", "scrollspeed"].includes(option) && Number(value) < 0) {
115
+ throw new Error(`${option} must be non-negative`);
116
+ }
117
+ if (option === "tabsize" && Number(value) <= 0) throw new Error("tabsize must be positive");
118
+ }
@@ -0,0 +1,102 @@
1
+ export const DEFAULT_COMMON_SETTINGS = {
2
+ autoindent: true,
3
+ autosu: false,
4
+ backup: true,
5
+ backupdir: "",
6
+ basename: false,
7
+ colorcolumn: 0,
8
+ cursorline: true,
9
+ detectlimit: 100,
10
+ diffgutter: false,
11
+ encoding: "utf-8",
12
+ eofnewline: true,
13
+ fastdirty: false,
14
+ fileformat: defaultFileFormat(),
15
+ filetype: "unknown",
16
+ hlsearch: false,
17
+ hltaberrors: false,
18
+ hltrailingws: false,
19
+ ignorecase: true,
20
+ incsearch: true,
21
+ indentchar: " ",
22
+ keepautoindent: false,
23
+ matchbrace: true,
24
+ matchbraceleft: true,
25
+ matchbracestyle: "underline",
26
+ mkparents: false,
27
+ pageoverlap: 2,
28
+ permbackup: false,
29
+ readonly: false,
30
+ relativeruler: false,
31
+ reload: "prompt",
32
+ rmtrailingws: false,
33
+ ruler: true,
34
+ savecursor: false,
35
+ saveundo: false,
36
+ scrollbar: false,
37
+ scrollmargin: 3,
38
+ scrollspeed: 2,
39
+ showchars: "",
40
+ smartpaste: true,
41
+ softwrap: false,
42
+ splitbottom: true,
43
+ splitright: true,
44
+ statusformatl: "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
45
+ statusformatr: "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
46
+ statusline: true,
47
+ syntax: true,
48
+ tabmovement: false,
49
+ tabsize: 4,
50
+ tabstospaces: false,
51
+ truecolor: "auto",
52
+ useprimary: true,
53
+ wordwrap: false,
54
+ };
55
+
56
+ export const DEFAULT_GLOBAL_ONLY_SETTINGS = {
57
+ autosave: 0,
58
+ clipboard: "external",
59
+ colorscheme: "default",
60
+ savehistory: true,
61
+ divchars: "|-",
62
+ divreverse: true,
63
+ fakecursor: process.platform === "win32",
64
+ helpsplit: "hsplit",
65
+ infobar: true,
66
+ keymenu: false,
67
+ lockbindings: false,
68
+ mouse: true,
69
+ multiopen: "tab",
70
+ parsecursor: false,
71
+ paste: false,
72
+ pluginchannels: ["https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"],
73
+ pluginrepos: [],
74
+ savehistory: true,
75
+ scrollbarchar: "|",
76
+ sucmd: "sudo",
77
+ tabhighlight: false,
78
+ tabreverse: true,
79
+ xterm: false,
80
+ };
81
+
82
+ export const OPTION_CHOICES = {
83
+ clipboard: ["internal", "external", "terminal"],
84
+ fileformat: ["unix", "dos"],
85
+ helpsplit: ["hsplit", "vsplit"],
86
+ matchbracestyle: ["underline", "highlight"],
87
+ multiopen: ["tab", "hsplit", "vsplit"],
88
+ reload: ["prompt", "auto", "disabled"],
89
+ truecolor: ["auto", "on", "off"],
90
+ };
91
+
92
+ // Settings that are buffer-local only and must never be written to the global config file.
93
+ // Mirrors Go micro's config.LocalSettings.
94
+ export const LOCAL_SETTINGS = new Set(["readonly", "filetype"]);
95
+
96
+ export function defaultAllSettings() {
97
+ return { ...DEFAULT_COMMON_SETTINGS, ...DEFAULT_GLOBAL_ONLY_SETTINGS };
98
+ }
99
+
100
+ function defaultFileFormat() {
101
+ return process.platform === "win32" ? "dos" : "unix";
102
+ }
@@ -0,0 +1,60 @@
1
+ const BASIC = {
2
+ black: 0,
3
+ red: 1,
4
+ green: 2,
5
+ yellow: 3,
6
+ blue: 4,
7
+ magenta: 5,
8
+ cyan: 6,
9
+ white: 7,
10
+ brightblack: 8,
11
+ lightblack: 8,
12
+ brightred: 9,
13
+ lightred: 9,
14
+ brightgreen: 10,
15
+ lightgreen: 10,
16
+ brightyellow: 11,
17
+ lightyellow: 11,
18
+ brightblue: 12,
19
+ lightblue: 12,
20
+ brightmagenta: 13,
21
+ lightmagenta: 13,
22
+ brightcyan: 14,
23
+ lightcyan: 14,
24
+ brightwhite: 15,
25
+ lightwhite: 15,
26
+ };
27
+
28
+ export function styleToAnsi(style = {}) {
29
+ const codes = [0];
30
+ if (style.bold) codes.push(1);
31
+ if (style.italic) codes.push(3);
32
+ if (style.underline) codes.push(4);
33
+ if (style.reverse) codes.push(7);
34
+ codes.push(...colorCodes(style.fg, false));
35
+ codes.push(...colorCodes(style.bg, true));
36
+ return `\x1b[${codes.join(";")}m`;
37
+ }
38
+
39
+ export function resetStyle() {
40
+ return "\x1b[0m";
41
+ }
42
+
43
+ export function styleResetTo(style = {}) {
44
+ return resetStyle() + styleToAnsi(style);
45
+ }
46
+
47
+ function colorCodes(color, background) {
48
+ if (color == null || color === "default") return [];
49
+ const base = background ? 48 : 38;
50
+ if (typeof color === "number") return [base, 5, color];
51
+ const value = String(color).toLowerCase();
52
+ if (/^#[0-9a-f]{6}$/.test(value)) {
53
+ const r = Number.parseInt(value.slice(1, 3), 16);
54
+ const g = Number.parseInt(value.slice(3, 5), 16);
55
+ const b = Number.parseInt(value.slice(5, 7), 16);
56
+ return [base, 2, r, g, b];
57
+ }
58
+ if (value in BASIC) return [base, 5, BASIC[value]];
59
+ return [];
60
+ }
@@ -0,0 +1,237 @@
1
+ export class Highlighter {
2
+ constructor(definition, definitions = []) {
3
+ this.definition = definition;
4
+ this.definitions = definitions;
5
+ this.rules = resolveRules(definition?.rules ?? [], definitions, new Set());
6
+ }
7
+
8
+ highlightLine(line, state = null, progress = null) {
9
+ const text = String(line);
10
+ const changes = new Map([[0, state?.group ?? "default"]]);
11
+ const nextState = state
12
+ ? scanRegion(changes, text, 0, state, this.rules, progress)
13
+ : scanTop(changes, text, 0, this.rules, progress);
14
+ return {
15
+ changes: normalizeChanges(changes, text.length),
16
+ state: nextState,
17
+ };
18
+ }
19
+
20
+ highlightString(input) {
21
+ const matches = [];
22
+ let state = null;
23
+ for (const line of String(input).split("\n")) {
24
+ const result = this.highlightLine(line, state);
25
+ matches.push(result.changes);
26
+ state = result.state;
27
+ }
28
+ return matches;
29
+ }
30
+ }
31
+
32
+ export function resolveRules(rules, definitions, seen) {
33
+ const out = [];
34
+ for (const rule of rules) {
35
+ if (rule.type === "include") {
36
+ if (seen.has(rule.include)) continue;
37
+ seen.add(rule.include);
38
+ const def = definitions.find((candidate) => candidate.filetype === rule.include);
39
+ if (def) out.push(...resolveRules(def.rules, definitions, seen));
40
+ } else if (rule.type === "region") {
41
+ out.push({ ...rule, rules: resolveRules(rule.rules ?? [], definitions, new Set(seen)) });
42
+ } else {
43
+ out.push(rule);
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+
49
+ export function flattenRules(rules, definitions, seen) {
50
+ return resolveRules(rules, definitions, seen).flatMap((rule) => {
51
+ if (rule.type === "region") return flattenRules(rule.rules ?? [], definitions, new Set(seen));
52
+ return rule.type === "pattern" ? [rule] : [];
53
+ });
54
+ }
55
+
56
+ function scanTop(changes, line, pos, rules, progress = null) {
57
+ let cursor = pos;
58
+ while (cursor <= line.length) {
59
+ progress?.(cursor);
60
+ const regionStart = findFirstRegion(rules, line, cursor, progress);
61
+ if (!regionStart) {
62
+ applyPatterns(changes, line, cursor, line.length, rules, "default", progress);
63
+ changes.set(line.length, "default");
64
+ progress?.(line.length);
65
+ return null;
66
+ }
67
+
68
+ applyPatterns(changes, line, cursor, regionStart.start, rules, "default", progress);
69
+ changes.set(regionStart.start, regionStart.region.limitGroup ?? "default");
70
+ changes.set(regionStart.end, regionStart.region.group);
71
+ progress?.(regionStart.end);
72
+
73
+ const state = scanRegion(changes, line, regionStart.end, regionStart.region, rules, progress);
74
+ if (state) return state;
75
+ return null;
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function scanRegion(changes, line, pos, region, rootRules, progress = null) {
81
+ let cursor = pos;
82
+ const rules = region.rules ?? [];
83
+ while (cursor <= line.length) {
84
+ progress?.(cursor);
85
+ const end = findMatch(region.end, line, cursor, region.skip, progress);
86
+ const nested = findFirstRegion(rules, line, cursor, progress);
87
+
88
+ if (end && (!nested || end.start <= nested.start)) {
89
+ applyPatterns(changes, line, cursor, end.start, rules, region.group, progress);
90
+ changes.set(end.start, region.limitGroup ?? region.group);
91
+ changes.set(end.end, parentGroup(region));
92
+ progress?.(end.end);
93
+ if (region.parent) return scanRegion(changes, line, end.end, region.parent, rootRules, progress);
94
+ return scanTop(changes, line, Math.max(end.end, end.start + 1), rootRules, progress);
95
+ }
96
+
97
+ if (nested) {
98
+ applyPatterns(changes, line, cursor, nested.start, rules, region.group, progress);
99
+ changes.set(nested.start, nested.region.limitGroup ?? region.group);
100
+ const child = { ...nested.region, parent: region };
101
+ changes.set(nested.end, child.group);
102
+ progress?.(nested.end);
103
+ const state = scanRegion(changes, line, nested.end, child, rootRules, progress);
104
+ if (state) return state;
105
+ cursor = Math.max(nested.end + 1, nextChangeAfter(changes, nested.end));
106
+ continue;
107
+ }
108
+
109
+ applyPatterns(changes, line, cursor, line.length, rules, region.group, progress);
110
+ changes.set(line.length, region.group);
111
+ progress?.(line.length);
112
+ return region;
113
+ }
114
+ return region;
115
+ }
116
+
117
+ function applyPatterns(changes, line, start, end, rules, fallbackGroup, progress = null) {
118
+ if (end < start) return;
119
+ const length = end - start;
120
+ if (length === 0) {
121
+ if (!changes.has(start)) changes.set(start, fallbackGroup);
122
+ return;
123
+ }
124
+ // Mirror Go's fullHighlights approach: later rules overwrite all positions in their match range,
125
+ // preventing earlier rules (e.g. symbol.operator matching "!") from leaking into spans that a
126
+ // later rule (e.g. the shebang comment pattern) should own entirely.
127
+ const fullHighlights = new Array(length).fill(null);
128
+ for (const rule of rules) {
129
+ if (rule.type !== "pattern" || !rule.regex) continue;
130
+ for (const match of findAllMatches(rule.regex, line, start, end, progress)) {
131
+ if (match.start === match.end) continue;
132
+ const lo = Math.max(match.start, start) - start;
133
+ const hi = Math.min(match.end, end) - start;
134
+ for (let i = lo; i < hi; i++) fullHighlights[i] = rule.group;
135
+ }
136
+ progress?.(end);
137
+ }
138
+ // Emit only color-change boundaries into the changes map (same as Go's transition loop).
139
+ let prev = changes.get(start) ?? fallbackGroup;
140
+ for (let i = 0; i < length; i++) {
141
+ const g = fullHighlights[i] ?? fallbackGroup;
142
+ if (g !== prev) { changes.set(start + i, g); prev = g; }
143
+ }
144
+ if (!changes.has(start)) changes.set(start, fullHighlights[0] ?? fallbackGroup);
145
+ if (!changes.has(end)) changes.set(end, fallbackGroup);
146
+ }
147
+
148
+ function findFirstRegion(rules, line, pos, progress = null) {
149
+ let best = null;
150
+ for (const region of rules.filter((rule) => rule.type === "region" && rule.start)) {
151
+ const match = findMatch(region.start, line, pos, region.skip, progress);
152
+ if (!match) continue;
153
+ if (!best || match.start < best.start) best = { ...match, region };
154
+ }
155
+ return best;
156
+ }
157
+
158
+ function findMatch(regex, line, pos, skip, progress = null) {
159
+ if (!regex) return null;
160
+ const masked = skip ? maskSkipped(line, skip) : line;
161
+ const global = globalize(regex);
162
+ global.lastIndex = pos;
163
+ let match;
164
+ if (!progress) {
165
+ match = global.exec(masked);
166
+ if (!match) return null;
167
+ const start = match.index ?? 0;
168
+ return { start, end: start + match[0].length };
169
+ }
170
+ while ((match = global.exec(masked)) !== null) {
171
+ const start = match.index ?? 0;
172
+ const end = start + match[0].length;
173
+ progress?.(start);
174
+ if (start >= pos) return { start, end };
175
+ if (match[0].length === 0) global.lastIndex++;
176
+ }
177
+ progress?.(line.length);
178
+ return null;
179
+ }
180
+
181
+ function findAllMatches(regex, line, start, end, progress = null) {
182
+ const out = [];
183
+ const global = globalize(regex);
184
+ global.lastIndex = start;
185
+ let match;
186
+ if (!progress) {
187
+ while ((match = global.exec(line)) !== null) {
188
+ const matchStart = match.index ?? 0;
189
+ if (matchStart >= end) break;
190
+ const matchEnd = matchStart + match[0].length;
191
+ if (matchEnd > start) out.push({ start: Math.max(matchStart, start), end: Math.min(matchEnd, end) });
192
+ if (match[0].length === 0) global.lastIndex++;
193
+ }
194
+ return out;
195
+ }
196
+ while ((match = global.exec(line)) !== null) {
197
+ const matchStart = match.index ?? 0;
198
+ const matchEnd = matchStart + match[0].length;
199
+ if (matchStart >= end) break;
200
+ progress?.(matchStart);
201
+ if (matchEnd <= start || matchStart >= end) continue;
202
+ out.push({ start: Math.max(matchStart, start), end: Math.min(matchEnd, end) });
203
+ if (match[0].length === 0) global.lastIndex++;
204
+ }
205
+ progress?.(end);
206
+ return out;
207
+ }
208
+
209
+ function maskSkipped(line, skip) {
210
+ return line.replace(globalize(skip), (value) => "\0".repeat(value.length));
211
+ }
212
+
213
+ function normalizeChanges(changes, lineLength) {
214
+ const normalized = new Map([...changes.entries()]
215
+ .filter(([index]) => index >= 0 && index <= lineLength)
216
+ .sort(([a], [b]) => a - b));
217
+ if (!normalized.has(0)) normalized.set(0, "default");
218
+ if (!normalized.has(lineLength)) normalized.set(lineLength, normalized.get([...normalized.keys()].at(-1)) ?? "default");
219
+ return new Map([...normalized.entries()].sort(([a], [b]) => a - b));
220
+ }
221
+
222
+ function parentGroup(region) {
223
+ return region.parent?.group ?? "default";
224
+ }
225
+
226
+ function nextChangeAfter(changes, index) {
227
+ for (const key of [...changes.keys()].sort((a, b) => a - b)) {
228
+ if (key > index) return key;
229
+ }
230
+ return index + 1;
231
+ }
232
+
233
+ function globalize(regex) {
234
+ const flags = new Set(regex.flags.split(""));
235
+ flags.add("g");
236
+ return new RegExp(regex.source, [...flags].join(""));
237
+ }
@@ -0,0 +1,137 @@
1
+ import { basename } from "node:path";
2
+
3
+ export class SyntaxHeader {
4
+ constructor({ filetype = "", filename = "", header = "", signature = "" } = {}) {
5
+ this.filetype = filetype;
6
+ this.filename = filename;
7
+ this.header = header;
8
+ this.signature = signature;
9
+ this.fileNameRegex = compileRegex(filename);
10
+ this.headerRegex = compileRegex(header);
11
+ this.signatureRegex = compileRegex(signature);
12
+ }
13
+
14
+ matchFileName(path) {
15
+ return !!this.fileNameRegex?.test(basename(path));
16
+ }
17
+
18
+ matchHeader(firstLine) {
19
+ return !!this.headerRegex?.test(firstLine);
20
+ }
21
+
22
+ hasSignature() {
23
+ return !!this.signatureRegex;
24
+ }
25
+
26
+ matchSignature(line) {
27
+ return !!this.signatureRegex?.test(line);
28
+ }
29
+ }
30
+
31
+ export class SyntaxDefinition {
32
+ constructor(header, source) {
33
+ this.header = header;
34
+ this.filetype = header.filetype;
35
+ this.source = source;
36
+ this.rules = parseRules(source.rules ?? []);
37
+ }
38
+ }
39
+
40
+ export async function loadSyntaxDefinitions(runtime) {
41
+ const headers = new Map();
42
+ for (const file of runtime.list(4)) {
43
+ headers.set(file.name, parseHeaderFile(await file.text()));
44
+ }
45
+
46
+ const definitions = [];
47
+ for (const file of runtime.list(1)) {
48
+ const source = Bun.YAML.parse(await file.text());
49
+ const header = headers.get(file.name) ?? parseHeaderYaml(source);
50
+ definitions.push(new SyntaxDefinition(header, source));
51
+ }
52
+ return definitions;
53
+ }
54
+
55
+ export function detectSyntax(definitions, { path = "", firstLine = "", lines = [] } = {}) {
56
+ for (const def of definitions) {
57
+ if (path && def.header.matchFileName(path)) return def;
58
+ }
59
+ for (const def of definitions) {
60
+ if (firstLine && def.header.matchHeader(firstLine)) return def;
61
+ }
62
+ for (const def of definitions) {
63
+ if (!def.header.hasSignature()) continue;
64
+ if (lines.some((line) => def.header.matchSignature(line))) return def;
65
+ }
66
+ return null;
67
+ }
68
+
69
+ export function parseHeaderFile(text) {
70
+ const [filetype = "", filename = "", header = "", signature = ""] = String(text).split(/\r?\n/);
71
+ return new SyntaxHeader({ filetype, filename, header, signature });
72
+ }
73
+
74
+ export function parseHeaderYaml(source) {
75
+ return new SyntaxHeader({
76
+ filetype: source?.filetype ?? "",
77
+ filename: source?.detect?.filename ?? "",
78
+ header: source?.detect?.header ?? "",
79
+ signature: source?.detect?.signature ?? "",
80
+ });
81
+ }
82
+
83
+ function parseRules(rules) {
84
+ return rules.map((rule) => parseRule(rule)).filter(Boolean);
85
+ }
86
+
87
+ function parseRule(rule) {
88
+ if (typeof rule === "string") return { type: "include", include: rule };
89
+ if (!rule || typeof rule !== "object") return null;
90
+ const [[group, value]] = Object.entries(rule);
91
+ if (group === "include") return { type: "include", include: value };
92
+ if (typeof value === "string") return { type: "pattern", group, regex: compileRegex(value), source: value };
93
+ if (value && typeof value === "object") {
94
+ return {
95
+ type: "region",
96
+ group,
97
+ start: compileRegex(value.start),
98
+ end: compileRegex(value.end),
99
+ skip: compileRegex(value.skip),
100
+ limitGroup: value["limit-group"] ?? group,
101
+ rules: parseRules(value.rules ?? []),
102
+ source: value,
103
+ };
104
+ }
105
+ return null;
106
+ }
107
+
108
+ function compileRegex(pattern) {
109
+ if (!pattern) return null;
110
+ const translated = translateGoRegexp(pattern);
111
+ try {
112
+ return new RegExp(translated, "u");
113
+ } catch {
114
+ try {
115
+ return new RegExp(translated);
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ }
121
+
122
+ function translateGoRegexp(pattern) {
123
+ return String(pattern)
124
+ .replaceAll("[[:alnum:]]", "[A-Za-z0-9]")
125
+ .replaceAll("[[:alpha:]]", "[A-Za-z]")
126
+ .replaceAll("[[:ascii:]]", "[\\x00-\\x7F]")
127
+ .replaceAll("[[:blank:]]", "[ \\t]")
128
+ .replaceAll("[[:cntrl:]]", "[\\x00-\\x1F\\x7F]")
129
+ .replaceAll("[[:digit:]]", "[0-9]")
130
+ .replaceAll("[[:graph:]]", "[!-~]")
131
+ .replaceAll("[[:lower:]]", "[a-z]")
132
+ .replaceAll("[[:print:]]", "[ -~]")
133
+ .replaceAll("[[:space:]]", "\\s")
134
+ .replaceAll("[[:upper:]]", "[A-Z]")
135
+ .replaceAll("[[:word:]]", "[A-Za-z0-9_]")
136
+ .replaceAll("[[:xdigit:]]", "[A-Fa-f0-9]");
137
+ }