@m4ike1/ion-tui 0.1.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 (196) hide show
  1. package/README.md +114 -0
  2. package/dist/alt-screen-search.d.ts +46 -0
  3. package/dist/alt-screen-search.d.ts.map +1 -0
  4. package/dist/alt-screen-search.js +282 -0
  5. package/dist/alt-screen-search.js.map +1 -0
  6. package/dist/autocomplete.d.ts +75 -0
  7. package/dist/autocomplete.d.ts.map +1 -0
  8. package/dist/autocomplete.js +749 -0
  9. package/dist/autocomplete.js.map +1 -0
  10. package/dist/components/alt-screen-flash.d.ts +13 -0
  11. package/dist/components/alt-screen-flash.d.ts.map +1 -0
  12. package/dist/components/alt-screen-flash.js +37 -0
  13. package/dist/components/alt-screen-flash.js.map +1 -0
  14. package/dist/components/box.d.ts +24 -0
  15. package/dist/components/box.d.ts.map +1 -0
  16. package/dist/components/box.js +133 -0
  17. package/dist/components/box.js.map +1 -0
  18. package/dist/components/cancellable-loader.d.ts +22 -0
  19. package/dist/components/cancellable-loader.d.ts.map +1 -0
  20. package/dist/components/cancellable-loader.js +35 -0
  21. package/dist/components/cancellable-loader.js.map +1 -0
  22. package/dist/components/editor.d.ts +266 -0
  23. package/dist/components/editor.d.ts.map +1 -0
  24. package/dist/components/editor.js +2087 -0
  25. package/dist/components/editor.js.map +1 -0
  26. package/dist/components/h-stack.d.ts +7 -0
  27. package/dist/components/h-stack.d.ts.map +1 -0
  28. package/dist/components/h-stack.js +43 -0
  29. package/dist/components/h-stack.js.map +1 -0
  30. package/dist/components/image.d.ts +28 -0
  31. package/dist/components/image.d.ts.map +1 -0
  32. package/dist/components/image.js +90 -0
  33. package/dist/components/image.js.map +1 -0
  34. package/dist/components/input.d.ts +48 -0
  35. package/dist/components/input.d.ts.map +1 -0
  36. package/dist/components/input.js +417 -0
  37. package/dist/components/input.js.map +1 -0
  38. package/dist/components/loader.d.ts +33 -0
  39. package/dist/components/loader.d.ts.map +1 -0
  40. package/dist/components/loader.js +76 -0
  41. package/dist/components/loader.js.map +1 -0
  42. package/dist/components/markdown.d.ts +103 -0
  43. package/dist/components/markdown.d.ts.map +1 -0
  44. package/dist/components/markdown.js +858 -0
  45. package/dist/components/markdown.js.map +1 -0
  46. package/dist/components/mouse-region.d.ts +12 -0
  47. package/dist/components/mouse-region.d.ts.map +1 -0
  48. package/dist/components/mouse-region.js +21 -0
  49. package/dist/components/mouse-region.js.map +1 -0
  50. package/dist/components/scroll-view.d.ts +68 -0
  51. package/dist/components/scroll-view.d.ts.map +1 -0
  52. package/dist/components/scroll-view.js +199 -0
  53. package/dist/components/scroll-view.js.map +1 -0
  54. package/dist/components/select-list.d.ts +53 -0
  55. package/dist/components/select-list.d.ts.map +1 -0
  56. package/dist/components/select-list.js +206 -0
  57. package/dist/components/select-list.js.map +1 -0
  58. package/dist/components/settings-list.d.ts +60 -0
  59. package/dist/components/settings-list.d.ts.map +1 -0
  60. package/dist/components/settings-list.js +252 -0
  61. package/dist/components/settings-list.js.map +1 -0
  62. package/dist/components/spacer.d.ts +12 -0
  63. package/dist/components/spacer.d.ts.map +1 -0
  64. package/dist/components/spacer.js +23 -0
  65. package/dist/components/spacer.js.map +1 -0
  66. package/dist/components/stack.d.ts +32 -0
  67. package/dist/components/stack.d.ts.map +1 -0
  68. package/dist/components/stack.js +111 -0
  69. package/dist/components/stack.js.map +1 -0
  70. package/dist/components/text.d.ts +19 -0
  71. package/dist/components/text.d.ts.map +1 -0
  72. package/dist/components/text.js +90 -0
  73. package/dist/components/text.js.map +1 -0
  74. package/dist/components/truncated-text.d.ts +13 -0
  75. package/dist/components/truncated-text.d.ts.map +1 -0
  76. package/dist/components/truncated-text.js +51 -0
  77. package/dist/components/truncated-text.js.map +1 -0
  78. package/dist/components/v-stack.d.ts +8 -0
  79. package/dist/components/v-stack.d.ts.map +1 -0
  80. package/dist/components/v-stack.js +26 -0
  81. package/dist/components/v-stack.js.map +1 -0
  82. package/dist/components/virtual-list.d.ts +51 -0
  83. package/dist/components/virtual-list.d.ts.map +1 -0
  84. package/dist/components/virtual-list.js +170 -0
  85. package/dist/components/virtual-list.js.map +1 -0
  86. package/dist/editor-component.d.ts +39 -0
  87. package/dist/editor-component.d.ts.map +1 -0
  88. package/dist/editor-component.js +2 -0
  89. package/dist/editor-component.js.map +1 -0
  90. package/dist/fuzzy.d.ts +16 -0
  91. package/dist/fuzzy.d.ts.map +1 -0
  92. package/dist/fuzzy.js +110 -0
  93. package/dist/fuzzy.js.map +1 -0
  94. package/dist/index.d.ts +34 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +46 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/keybindings.d.ts +366 -0
  99. package/dist/keybindings.d.ts.map +1 -0
  100. package/dist/keybindings.js +331 -0
  101. package/dist/keybindings.js.map +1 -0
  102. package/dist/keys.d.ts +184 -0
  103. package/dist/keys.d.ts.map +1 -0
  104. package/dist/keys.js +1174 -0
  105. package/dist/keys.js.map +1 -0
  106. package/dist/kill-ring.d.ts +28 -0
  107. package/dist/kill-ring.d.ts.map +1 -0
  108. package/dist/kill-ring.js +44 -0
  109. package/dist/kill-ring.js.map +1 -0
  110. package/dist/latex.d.ts +10 -0
  111. package/dist/latex.d.ts.map +1 -0
  112. package/dist/latex.js +1285 -0
  113. package/dist/latex.js.map +1 -0
  114. package/dist/layout-node.d.ts +59 -0
  115. package/dist/layout-node.d.ts.map +1 -0
  116. package/dist/layout-node.js +12 -0
  117. package/dist/layout-node.js.map +1 -0
  118. package/dist/layout.d.ts +42 -0
  119. package/dist/layout.d.ts.map +1 -0
  120. package/dist/layout.js +404 -0
  121. package/dist/layout.js.map +1 -0
  122. package/dist/native-modifiers.d.ts +4 -0
  123. package/dist/native-modifiers.d.ts.map +1 -0
  124. package/dist/native-modifiers.js +13 -0
  125. package/dist/native-modifiers.js.map +1 -0
  126. package/dist/native-module-path.d.ts +7 -0
  127. package/dist/native-module-path.d.ts.map +1 -0
  128. package/dist/native-module-path.js +19 -0
  129. package/dist/native-module-path.js.map +1 -0
  130. package/dist/native-platform.d.ts +18 -0
  131. package/dist/native-platform.d.ts.map +1 -0
  132. package/dist/native-platform.js +42 -0
  133. package/dist/native-platform.js.map +1 -0
  134. package/dist/pty-host.d.ts +224 -0
  135. package/dist/pty-host.d.ts.map +1 -0
  136. package/dist/pty-host.js +347 -0
  137. package/dist/pty-host.js.map +1 -0
  138. package/dist/stdin-buffer.d.ts +56 -0
  139. package/dist/stdin-buffer.d.ts.map +1 -0
  140. package/dist/stdin-buffer.js +366 -0
  141. package/dist/stdin-buffer.js.map +1 -0
  142. package/dist/terminal-colors.d.ts +10 -0
  143. package/dist/terminal-colors.d.ts.map +1 -0
  144. package/dist/terminal-colors.js +59 -0
  145. package/dist/terminal-colors.js.map +1 -0
  146. package/dist/terminal-image.d.ts +118 -0
  147. package/dist/terminal-image.d.ts.map +1 -0
  148. package/dist/terminal-image.js +550 -0
  149. package/dist/terminal-image.js.map +1 -0
  150. package/dist/terminal.d.ts +123 -0
  151. package/dist/terminal.d.ts.map +1 -0
  152. package/dist/terminal.js +442 -0
  153. package/dist/terminal.js.map +1 -0
  154. package/dist/tui-alt-screen.d.ts +165 -0
  155. package/dist/tui-alt-screen.d.ts.map +1 -0
  156. package/dist/tui-alt-screen.js +1515 -0
  157. package/dist/tui-alt-screen.js.map +1 -0
  158. package/dist/tui-main-screen.d.ts +39 -0
  159. package/dist/tui-main-screen.d.ts.map +1 -0
  160. package/dist/tui-main-screen.js +594 -0
  161. package/dist/tui-main-screen.js.map +1 -0
  162. package/dist/tui.d.ts +431 -0
  163. package/dist/tui.d.ts.map +1 -0
  164. package/dist/tui.js +1142 -0
  165. package/dist/tui.js.map +1 -0
  166. package/dist/undo-stack.d.ts +17 -0
  167. package/dist/undo-stack.d.ts.map +1 -0
  168. package/dist/undo-stack.js +25 -0
  169. package/dist/undo-stack.js.map +1 -0
  170. package/dist/utils.d.ts +98 -0
  171. package/dist/utils.d.ts.map +1 -0
  172. package/dist/utils.js +1200 -0
  173. package/dist/utils.js.map +1 -0
  174. package/dist/word-navigation.d.ts +25 -0
  175. package/dist/word-navigation.d.ts.map +1 -0
  176. package/dist/word-navigation.js +96 -0
  177. package/dist/word-navigation.js.map +1 -0
  178. package/native/clipboard.h +170 -0
  179. package/native/darwin/README.md +22 -0
  180. package/native/darwin/build.sh +69 -0
  181. package/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  182. package/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  183. package/native/darwin/src/darwin-platform.m +93 -0
  184. package/native/linux/README.md +23 -0
  185. package/native/linux/build.sh +65 -0
  186. package/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  187. package/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  188. package/native/linux/src/clipboard-worker.h +70 -0
  189. package/native/linux/src/linux-platform-x11.c +274 -0
  190. package/native/napi.h +89 -0
  191. package/native/win32/README.md +31 -0
  192. package/native/win32/build.mjs +232 -0
  193. package/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  194. package/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  195. package/native/win32/src/win32-platform.c +241 -0
  196. package/package.json +62 -0
@@ -0,0 +1,232 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import * as path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
8
+ const sourceFile = path.join(scriptDir, "src", "win32-platform.c");
9
+ const temporaryDir = mkdtempSync(path.join(tmpdir(), "ion-tui-win32-"));
10
+
11
+ const targets = [
12
+ {
13
+ arch: "x64",
14
+ msvcArch: "x64",
15
+ mingwTarget: "x86_64-w64-windows-gnu",
16
+ prefixedCompilers: ["x86_64-w64-mingw32-gcc", "x86_64-w64-mingw32-clang"],
17
+ envCompiler: "CC_X64",
18
+ },
19
+ {
20
+ arch: "arm64",
21
+ msvcArch: "arm64",
22
+ mingwTarget: "aarch64-w64-windows-gnu",
23
+ prefixedCompilers: ["aarch64-w64-mingw32-gcc", "aarch64-w64-mingw32-clang"],
24
+ envCompiler: "CC_ARM64",
25
+ },
26
+ ];
27
+
28
+ function removeTemporaryDir() {
29
+ rmSync(temporaryDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
30
+ }
31
+
32
+ process.on("exit", removeTemporaryDir);
33
+ process.on("SIGINT", () => {
34
+ removeTemporaryDir();
35
+ process.exit(130);
36
+ });
37
+
38
+ function commandExists(command) {
39
+ const result = spawnSync(command, ["--version"], { stdio: "ignore" });
40
+ return !result.error || result.error.code !== "ENOENT";
41
+ }
42
+
43
+ function run(command, args, options = {}) {
44
+ const result = spawnSync(command, args, { stdio: "inherit", ...options });
45
+ if (result.error) throw result.error;
46
+ if (result.status !== 0) throw new Error(`${command} failed with exit code ${result.status ?? 1}`);
47
+ }
48
+
49
+ function quoteBatch(value) {
50
+ return `"${value.replaceAll('"', '""')}"`;
51
+ }
52
+
53
+ function msvcHostArch() {
54
+ return process.arch === "arm64" ? "arm64" : "x64";
55
+ }
56
+
57
+ function findVisualStudioDevCmd() {
58
+ const candidates = [];
59
+ const programFilesX86 = process.env["ProgramFiles(x86)"];
60
+ if (programFilesX86) {
61
+ const vswhere = path.join(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
62
+ if (existsSync(vswhere)) {
63
+ const result = spawnSync(
64
+ vswhere,
65
+ [
66
+ "-latest",
67
+ "-products",
68
+ "*",
69
+ "-requires",
70
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
71
+ "-property",
72
+ "installationPath",
73
+ ],
74
+ { encoding: "utf8" },
75
+ );
76
+ for (const line of result.stdout.split(/\r?\n/)) {
77
+ const installationPath = line.trim();
78
+ if (installationPath) candidates.push(path.join(installationPath, "Common7", "Tools", "VsDevCmd.bat"));
79
+ }
80
+ }
81
+ }
82
+
83
+ for (const base of [process.env.ProgramFiles, programFilesX86]) {
84
+ if (!base) continue;
85
+ for (const edition of ["BuildTools", "Community", "Professional", "Enterprise"]) {
86
+ candidates.push(path.join(base, "Microsoft Visual Studio", "2022", edition, "Common7", "Tools", "VsDevCmd.bat"));
87
+ }
88
+ }
89
+
90
+ return candidates.find((candidate) => existsSync(candidate));
91
+ }
92
+
93
+ function canUseMsvc(vsDevCmd) {
94
+ const probeDir = path.join(temporaryDir, "msvc-probe");
95
+ mkdirSync(probeDir, { recursive: true });
96
+ const batchFile = path.join(probeDir, "probe.cmd");
97
+ writeFileSync(
98
+ batchFile,
99
+ [
100
+ "@echo off",
101
+ `call ${quoteBatch(vsDevCmd)} -no_logo -arch=x64 -host_arch=${msvcHostArch()} >nul`,
102
+ "if errorlevel 1 exit /b %errorlevel%",
103
+ "where cl.exe >nul 2>nul",
104
+ ].join("\r\n"),
105
+ );
106
+ const result = spawnSync("cmd.exe", ["/d", "/s", "/c", batchFile], { cwd: probeDir, stdio: "ignore" });
107
+ return !result.error && result.status === 0;
108
+ }
109
+
110
+ function resolveMingwCompiler(target) {
111
+ const envCompiler = process.env[target.envCompiler];
112
+ if (envCompiler) return { command: envCompiler, useTargetFlag: false };
113
+ if (process.env.CC) return { command: process.env.CC, useTargetFlag: true };
114
+ for (const command of target.prefixedCompilers) {
115
+ if (commandExists(command)) return { command, useTargetFlag: false };
116
+ }
117
+ if (commandExists("clang")) return { command: "clang", useTargetFlag: true };
118
+ return undefined;
119
+ }
120
+
121
+ function installOutput(temporaryOutput, target) {
122
+ const outputDir = path.join(scriptDir, "prebuilds", `win32-${target.arch}`);
123
+ mkdirSync(outputDir, { recursive: true });
124
+ const output = path.join(outputDir, "win32-platform.node");
125
+ copyFileSync(temporaryOutput, output);
126
+ chmodSync(output, 0o755);
127
+ console.log(`Built ${path.relative(process.cwd(), output)}`);
128
+ }
129
+
130
+ function buildWithMsvc(target, vsDevCmd) {
131
+ const buildDir = path.join(temporaryDir, `msvc-${target.arch}`);
132
+ const temporaryOutput = path.join(buildDir, "win32-platform.node");
133
+ mkdirSync(buildDir, { recursive: true });
134
+
135
+ const batchFile = path.join(buildDir, "build.cmd");
136
+ writeFileSync(
137
+ batchFile,
138
+ [
139
+ "@echo off",
140
+ `call ${quoteBatch(vsDevCmd)} -no_logo -arch=${target.msvcArch} -host_arch=${msvcHostArch()}`,
141
+ "if errorlevel 1 exit /b %errorlevel%",
142
+ [
143
+ "cl",
144
+ "/nologo",
145
+ "/TC",
146
+ "/std:c11",
147
+ "/W4",
148
+ "/O1",
149
+ "/GS-",
150
+ "/LD",
151
+ `/Fe:${quoteBatch(temporaryOutput)}`,
152
+ quoteBatch(sourceFile),
153
+ "/link",
154
+ "/NOENTRY",
155
+ "/NODEFAULTLIB",
156
+ "kernel32.lib",
157
+ "user32.lib",
158
+ "/OPT:REF",
159
+ "/OPT:ICF",
160
+ ].join(" "),
161
+ "if errorlevel 1 exit /b %errorlevel%",
162
+ ].join("\r\n"),
163
+ );
164
+
165
+ run("cmd.exe", ["/d", "/s", "/c", batchFile], { cwd: buildDir });
166
+ installOutput(temporaryOutput, target);
167
+ }
168
+
169
+ function buildWithMingw(target) {
170
+ const compiler = resolveMingwCompiler(target);
171
+ if (!compiler) {
172
+ throw new Error(
173
+ `No Windows cross-compiler found for ${target.arch}. Install Microsoft C++ Build Tools on Windows, or set ${target.envCompiler} to a MinGW-compatible compiler.`,
174
+ );
175
+ }
176
+
177
+ const buildDir = path.join(temporaryDir, `mingw-${target.arch}`);
178
+ const temporaryOutput = path.join(buildDir, "win32-platform.node");
179
+ mkdirSync(buildDir, { recursive: true });
180
+
181
+ const args = [
182
+ ...(compiler.useTargetFlag ? [`--target=${target.mingwTarget}`] : []),
183
+ "-std=c11",
184
+ "-Wall",
185
+ "-Wextra",
186
+ "-Oz",
187
+ "-fno-builtin",
188
+ "-shared",
189
+ "-nostdlib",
190
+ "-Wl,--no-entry",
191
+ "-Wl,--strip-all",
192
+ sourceFile,
193
+ "-lkernel32",
194
+ "-luser32",
195
+ "-o",
196
+ temporaryOutput,
197
+ ];
198
+
199
+ run(compiler.command, args, { cwd: buildDir });
200
+ installOutput(temporaryOutput, target);
201
+ }
202
+
203
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
204
+ console.log(`Usage: npm --prefix packages/tui run build:native:win32
205
+
206
+ Builds win32-x64 and win32-arm64 native prebuilds.
207
+
208
+ Environment:
209
+ ION_TUI_WIN32_TOOLCHAIN=msvc|mingw
210
+ CC_X64=/path/to/x86_64-w64-mingw32-gcc
211
+ CC_ARM64=/path/to/aarch64-w64-mingw32-gcc
212
+ CC=/path/to/clang`);
213
+ process.exit(0);
214
+ }
215
+
216
+ const requestedToolchain = process.env.ION_TUI_WIN32_TOOLCHAIN;
217
+ if (requestedToolchain && requestedToolchain !== "msvc" && requestedToolchain !== "mingw") {
218
+ throw new Error("ION_TUI_WIN32_TOOLCHAIN must be either 'msvc' or 'mingw'");
219
+ }
220
+
221
+ const vsDevCmd = findVisualStudioDevCmd();
222
+ const hasMsvc = vsDevCmd ? canUseMsvc(vsDevCmd) : false;
223
+ const toolchain = requestedToolchain ?? (process.platform === "win32" && vsDevCmd ? "msvc" : "mingw");
224
+
225
+ if (toolchain === "msvc") {
226
+ if (!vsDevCmd || !hasMsvc) {
227
+ throw new Error("Microsoft C++ Build Tools not found. Install the Visual Studio C++ workload or set ION_TUI_WIN32_TOOLCHAIN=mingw.");
228
+ }
229
+ for (const target of targets) buildWithMsvc(target, vsDevCmd);
230
+ } else {
231
+ for (const target of targets) buildWithMingw(target);
232
+ }
@@ -0,0 +1,241 @@
1
+ #define PI_CLIPBOARD_WRITE
2
+ #include "../../clipboard.h"
3
+
4
+ #ifndef BI_ALPHABITFIELDS
5
+ #define BI_ALPHABITFIELDS 6
6
+ #endif
7
+
8
+ #define KEY_PRESSED_MASK 0x8000
9
+ #define OPEN_CLIPBOARD_ATTEMPTS 10
10
+ #define OPEN_CLIPBOARD_RETRY_MS 5
11
+ #define BITMAP_FILE_HEADER_SIZE 14
12
+
13
+ static int string_equals(const char* left, const char* right) {
14
+ while (*left && *right && *left == *right) {
15
+ left++;
16
+ right++;
17
+ }
18
+ return *left == 0 && *right == 0;
19
+ }
20
+
21
+ static int is_key_pressed(int virtual_key) {
22
+ return ((unsigned short)GetAsyncKeyState(virtual_key) & KEY_PRESSED_MASK) != 0;
23
+ }
24
+
25
+ static int is_modifier_name_pressed(const char* name) {
26
+ if (string_equals(name, "shift")) return is_key_pressed(VK_SHIFT) || is_key_pressed(VK_LSHIFT) || is_key_pressed(VK_RSHIFT);
27
+ if (string_equals(name, "control")) return is_key_pressed(VK_CONTROL) || is_key_pressed(VK_LCONTROL) || is_key_pressed(VK_RCONTROL);
28
+ if (string_equals(name, "option") || string_equals(name, "alt")) return is_key_pressed(VK_MENU) || is_key_pressed(VK_LMENU) || is_key_pressed(VK_RMENU);
29
+ if (string_equals(name, "command") || string_equals(name, "super") || string_equals(name, "win")) return is_key_pressed(VK_LWIN) || is_key_pressed(VK_RWIN);
30
+ return 0;
31
+ }
32
+
33
+ static napi_value __cdecl enable_virtual_terminal_input(napi_env env, napi_callback_info info) {
34
+ (void)info;
35
+
36
+ HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
37
+ DWORD mode = 0;
38
+ bool enabled = handle != INVALID_HANDLE_VALUE &&
39
+ GetConsoleMode(handle, &mode) &&
40
+ SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
41
+
42
+ napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
43
+ napi_value result = 0;
44
+ if (!napi_get_boolean || napi_get_boolean(env, enabled, &result) != 0) {
45
+ return fail(env, "Could not configure console input");
46
+ }
47
+ return result;
48
+ }
49
+
50
+ static napi_value __cdecl is_modifier_pressed(napi_env env, napi_callback_info info) {
51
+ napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
52
+ napi_get_value_string_utf8_fn napi_get_value_string_utf8 =
53
+ (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
54
+ napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
55
+
56
+ bool pressed = false;
57
+ if (napi_get_cb_info && napi_get_value_string_utf8) {
58
+ size_t argc = 1;
59
+ napi_value args[1] = {0};
60
+ if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
61
+ char name[16] = {0};
62
+ size_t copied = 0;
63
+ if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
64
+ pressed = is_modifier_name_pressed(name);
65
+ }
66
+ }
67
+ }
68
+
69
+ napi_value result = 0;
70
+ if (!napi_get_boolean || napi_get_boolean(env, pressed, &result) != 0) {
71
+ return fail(env, "Could not inspect modifier state");
72
+ }
73
+ return result;
74
+ }
75
+
76
+ static bool open_clipboard(HWND owner) {
77
+ for (int attempt = 0; attempt < OPEN_CLIPBOARD_ATTEMPTS; attempt++) {
78
+ if (OpenClipboard(owner)) return true;
79
+ Sleep(OPEN_CLIPBOARD_RETRY_MS);
80
+ }
81
+ return false;
82
+ }
83
+
84
+ static uint16_t read_u16(const uint8_t* data) {
85
+ return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
86
+ }
87
+
88
+ static uint32_t read_u32(const uint8_t* data) {
89
+ return (uint32_t)data[0] |
90
+ ((uint32_t)data[1] << 8) |
91
+ ((uint32_t)data[2] << 16) |
92
+ ((uint32_t)data[3] << 24);
93
+ }
94
+
95
+ static void write_u32(uint8_t* data, uint32_t value) {
96
+ data[0] = (uint8_t)value;
97
+ data[1] = (uint8_t)(value >> 8);
98
+ data[2] = (uint8_t)(value >> 16);
99
+ data[3] = (uint8_t)(value >> 24);
100
+ }
101
+
102
+ static void copy_bytes(uint8_t* destination, const uint8_t* source, size_t length) {
103
+ volatile uint8_t* target = destination;
104
+ const volatile uint8_t* input = source;
105
+ for (size_t index = 0; index < length; index++) target[index] = input[index];
106
+ }
107
+
108
+ static size_t dib_pixel_offset(const uint8_t* dib, size_t size) {
109
+ if (size < 12) return 0;
110
+ uint32_t header_size = read_u32(dib);
111
+
112
+ if (header_size == 12) {
113
+ uint16_t bits_per_pixel = read_u16(dib + 10);
114
+ uint32_t color_count = bits_per_pixel <= 8 ? 1u << bits_per_pixel : 0;
115
+ size_t offset = header_size + (size_t)color_count * 3;
116
+ return offset <= size ? offset : 0;
117
+ }
118
+
119
+ if (header_size < 40 || header_size > size) return 0;
120
+ uint16_t bits_per_pixel = read_u16(dib + 14);
121
+ uint32_t compression = read_u32(dib + 16);
122
+ uint32_t color_count = read_u32(dib + 32);
123
+ if (!color_count && bits_per_pixel <= 8) color_count = 1u << bits_per_pixel;
124
+
125
+ size_t offset = header_size + (size_t)color_count * 4;
126
+ if (header_size == 40 && compression == BI_BITFIELDS) offset += 3 * sizeof(uint32_t);
127
+ if (header_size == 40 && compression == BI_ALPHABITFIELDS) offset += 4 * sizeof(uint32_t);
128
+ return offset <= size ? offset : 0;
129
+ }
130
+
131
+ static void read_clipboard_image(clipboard_job* job) {
132
+ UINT png_format = RegisterClipboardFormatW(L"PNG");
133
+ if (!(png_format && IsClipboardFormatAvailable(png_format)) &&
134
+ !IsClipboardFormatAvailable(CF_DIBV5) && !IsClipboardFormatAvailable(CF_DIB)) {
135
+ job->format = CLIPBOARD_EMPTY;
136
+ return;
137
+ }
138
+ HGLOBAL handle = png_format && IsClipboardFormatAvailable(png_format)
139
+ ? (HGLOBAL)GetClipboardData(png_format) : 0;
140
+ bool is_png = handle != 0;
141
+ if (!handle && IsClipboardFormatAvailable(CF_DIBV5)) handle = (HGLOBAL)GetClipboardData(CF_DIBV5);
142
+ if (!handle && IsClipboardFormatAvailable(CF_DIB)) handle = (HGLOBAL)GetClipboardData(CF_DIB);
143
+
144
+ const uint8_t* source = handle ? (const uint8_t*)GlobalLock(handle) : 0;
145
+ size_t size = handle ? GlobalSize(handle) : 0;
146
+ if (!source || !size) {
147
+ if (source) GlobalUnlock(handle);
148
+ job->error = "Clipboard does not contain an image";
149
+ return;
150
+ }
151
+ if (is_png) {
152
+ clipboard_copy(job, source, size, CLIPBOARD_BUFFER);
153
+ } else {
154
+ size_t offset = dib_pixel_offset(source, size);
155
+ uint8_t* bitmap = offset && size <= UINT32_MAX - BITMAP_FILE_HEADER_SIZE
156
+ ? clipboard_alloc(size + BITMAP_FILE_HEADER_SIZE) : 0;
157
+ if (bitmap) {
158
+ bitmap[0] = 'B';
159
+ bitmap[1] = 'M';
160
+ write_u32(bitmap + 2, (uint32_t)(size + BITMAP_FILE_HEADER_SIZE));
161
+ write_u32(bitmap + 10, (uint32_t)(BITMAP_FILE_HEADER_SIZE + offset));
162
+ copy_bytes(bitmap + BITMAP_FILE_HEADER_SIZE, source, size);
163
+ job->data = bitmap;
164
+ job->length = size + BITMAP_FILE_HEADER_SIZE;
165
+ job->format = CLIPBOARD_BUFFER;
166
+ } else {
167
+ job->error = "Could not create clipboard image buffer";
168
+ }
169
+ }
170
+ GlobalUnlock(handle);
171
+ }
172
+
173
+ static void clipboard_execute(clipboard_job* job) {
174
+ // A real clipboard owner is required for text writes, including under Node
175
+ // without a console. Create and destroy it on the same worker thread.
176
+ HWND owner = 0;
177
+ if (job->operation == CLIPBOARD_WRITE) {
178
+ owner = CreateWindowExW(0, L"STATIC", 0, 0, 0, 0, 0, 0,
179
+ HWND_MESSAGE, 0, GetModuleHandleA(0), 0);
180
+ if (!owner) {
181
+ job->error = "Could not create clipboard owner window";
182
+ return;
183
+ }
184
+ }
185
+ if (!open_clipboard(owner)) {
186
+ if (owner) DestroyWindow(owner);
187
+ job->error = "Could not open clipboard";
188
+ return;
189
+ }
190
+ if (job->operation == CLIPBOARD_WRITE) {
191
+ size_t size = (job->length + 1) * sizeof(uint16_t);
192
+ HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, size);
193
+ void* text = handle ? GlobalLock(handle) : 0;
194
+ bool success = false;
195
+ if (text) {
196
+ copy_bytes(text, job->data, size);
197
+ GlobalUnlock(handle);
198
+ success = EmptyClipboard() && SetClipboardData(CF_UNICODETEXT, handle) != 0;
199
+ }
200
+ if (!success) {
201
+ if (handle) GlobalFree(handle);
202
+ job->error = "Could not set clipboard text";
203
+ }
204
+ } else if (job->operation == CLIPBOARD_TEXT) {
205
+ if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) {
206
+ job->format = CLIPBOARD_EMPTY;
207
+ } else {
208
+ HGLOBAL handle = (HGLOBAL)GetClipboardData(CF_UNICODETEXT);
209
+ const uint16_t* text = handle ? GlobalLock(handle) : 0;
210
+ if (text) {
211
+ size_t capacity = GlobalSize(handle) / sizeof(uint16_t);
212
+ size_t length = 0;
213
+ while (length < capacity && text[length]) length++;
214
+ clipboard_copy(job, text, length * sizeof(uint16_t), CLIPBOARD_UTF16);
215
+ GlobalUnlock(handle);
216
+ } else {
217
+ job->error = "Clipboard does not contain text";
218
+ }
219
+ }
220
+ } else {
221
+ read_clipboard_image(job);
222
+ }
223
+ CloseClipboard();
224
+ if (owner) DestroyWindow(owner);
225
+ }
226
+
227
+ BOOL WINAPI _DllMainCRTStartup(HINSTANCE instance, DWORD reason, LPVOID reserved) {
228
+ (void)instance;
229
+ (void)reason;
230
+ (void)reserved;
231
+ return TRUE;
232
+ }
233
+
234
+ PI_NAPI_EXPORT napi_value PI_NAPI_CALL napi_register_module_v1(napi_env env, napi_value exports) {
235
+ set_function_export(env, exports, "enableVirtualTerminalInput", enable_virtual_terminal_input);
236
+ set_function_export(env, exports, "isModifierPressed", is_modifier_pressed);
237
+ set_function_export(env, exports, "getText", get_clipboard_text);
238
+ set_function_export(env, exports, "setText", set_clipboard_text);
239
+ set_function_export(env, exports, "getImage", get_clipboard_image);
240
+ return exports;
241
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@m4ike1/ion-tui",
3
+ "version": "0.1.0",
4
+ "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "clean": "shx rm -rf dist",
9
+ "build": "tsgo -p tsconfig.build.json",
10
+ "build:native:darwin": "native/darwin/build.sh",
11
+ "build:native:linux": "native/linux/build.sh",
12
+ "build:native:win32": "node native/win32/build.mjs",
13
+ "test": "node --test --test-reporter=dot --test-reporter-destination=stdout test/*.test.ts",
14
+ "prepublishOnly": "npm run clean && npm run build"
15
+ },
16
+ "files": [
17
+ "dist/**/*",
18
+ "native/napi.h",
19
+ "native/clipboard.h",
20
+ "native/win32/prebuilds/**/*.node",
21
+ "native/win32/src/**/*.c",
22
+ "native/win32/build.mjs",
23
+ "native/win32/README.md",
24
+ "native/darwin/prebuilds/**/*.node",
25
+ "native/darwin/src/**/*.m",
26
+ "native/darwin/build.sh",
27
+ "native/darwin/README.md",
28
+ "native/linux/prebuilds/**/*.node",
29
+ "native/linux/src/**/*.{c,h}",
30
+ "native/linux/build.sh",
31
+ "native/linux/README.md",
32
+ "README.md"
33
+ ],
34
+ "keywords": [
35
+ "tui",
36
+ "terminal",
37
+ "ui",
38
+ "text-editor",
39
+ "differential-rendering",
40
+ "typescript",
41
+ "cli"
42
+ ],
43
+ "author": "Mario Zechner",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/maikel/ion.git",
48
+ "directory": "packages/tui"
49
+ },
50
+ "engines": {
51
+ "node": ">=22.19.0"
52
+ },
53
+ "types": "./dist/index.d.ts",
54
+ "dependencies": {
55
+ "get-east-asian-width": "1.6.0",
56
+ "marked": "18.0.11"
57
+ },
58
+ "devDependencies": {
59
+ "@xterm/headless": "5.5.0",
60
+ "chalk": "6.0.0"
61
+ }
62
+ }