@adhdev/ghostty-vt-node 0.7.35 → 0.8.2

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 (30) hide show
  1. package/index.d.ts +2 -0
  2. package/index.js +1 -1
  3. package/package.json +1 -1
  4. package/prebuilt/darwin-arm64-node115/ghostty_vt_node.node +0 -0
  5. package/prebuilt/darwin-arm64-node127/ghostty_vt_node.node +0 -0
  6. package/prebuilt/darwin-arm64-node137/ghostty_vt_node.node +0 -0
  7. package/prebuilt/darwin-arm64-node141/ghostty_vt_node.node +0 -0
  8. package/prebuilt/darwin-arm64-node141/libghostty-vt.0.1.0.dylib +0 -0
  9. package/prebuilt/darwin-arm64-node141/libghostty-vt.dylib +0 -0
  10. package/prebuilt/linux-x64-node115/ghostty_vt_node.node +0 -0
  11. package/prebuilt/linux-x64-node115/libghostty-vt.so +0 -0
  12. package/prebuilt/linux-x64-node115/libghostty-vt.so.0.1.0 +0 -0
  13. package/prebuilt/linux-x64-node127/ghostty_vt_node.node +0 -0
  14. package/prebuilt/linux-x64-node127/libghostty-vt.so +0 -0
  15. package/prebuilt/linux-x64-node127/libghostty-vt.so.0.1.0 +0 -0
  16. package/prebuilt/linux-x64-node137/ghostty_vt_node.node +0 -0
  17. package/prebuilt/linux-x64-node137/libghostty-vt.so +0 -0
  18. package/prebuilt/linux-x64-node137/libghostty-vt.so.0.1.0 +0 -0
  19. package/prebuilt/linux-x64-node141/ghostty_vt_node.node +0 -0
  20. package/prebuilt/linux-x64-node141/libghostty-vt.so +0 -0
  21. package/prebuilt/linux-x64-node141/libghostty-vt.so.0.1.0 +0 -0
  22. package/prebuilt/win32-x64-node115/ghostty_vt_node.node +0 -0
  23. package/prebuilt/win32-x64-node127/ghostty_vt_node.node +0 -0
  24. package/prebuilt/win32-x64-node137/ghostty_vt_node.node +0 -0
  25. package/prebuilt/win32-x64-node141/ghostty-vt.dll +0 -0
  26. package/prebuilt/win32-x64-node141/ghostty_vt_node.node +0 -0
  27. package/scripts/build-or-use-prebuilt.mjs +30 -3
  28. package/src/addon.cc +45 -0
  29. package/src/ghostty_bridge.c +58 -0
  30. package/src/ghostty_bridge.h +10 -0
package/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface GhosttyTerminalHandle {
12
12
  write(data: string | Uint8Array): void;
13
13
  resize(cols: number, rows: number): void;
14
14
  formatPlainText(options?: GhosttyPlainTextFormatOptions): string;
15
+ formatVT(): string;
16
+ getCursorPosition(): { col: number; row: number };
15
17
  dispose(): void;
16
18
  }
17
19
 
package/index.js CHANGED
@@ -12,9 +12,9 @@ function loadBinding() {
12
12
  const candidates = [
13
13
  explicitPrebuiltDir ? path.join(explicitPrebuiltDir, triplet, 'ghostty_vt_node.node') : null,
14
14
  explicitPrebuiltDir ? path.join(explicitPrebuiltDir, 'ghostty_vt_node.node') : null,
15
- path.join(__dirname, 'prebuilt', triplet, 'ghostty_vt_node.node'),
16
15
  path.join(__dirname, 'build', 'Release', 'ghostty_vt_node.node'),
17
16
  path.join(__dirname, 'build', 'Debug', 'ghostty_vt_node.node'),
17
+ path.join(__dirname, 'prebuilt', triplet, 'ghostty_vt_node.node'),
18
18
  ].filter(Boolean);
19
19
 
20
20
  const errors = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/ghostty-vt-node",
3
- "version": "0.7.35",
3
+ "version": "0.8.2",
4
4
  "description": "Native Node binding for libghostty-vt",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -8,6 +8,24 @@ const packageDir = path.resolve(scriptDir, '..');
8
8
  const outputDir = path.join(packageDir, 'build', 'Release');
9
9
  const outputFile = path.join(outputDir, 'ghostty_vt_node.node');
10
10
  const triplet = `${process.platform}-${process.arch}-node${process.versions.modules}`;
11
+ const sourceInputs = [
12
+ path.join(packageDir, 'CMakeLists.txt'),
13
+ path.join(packageDir, 'src', 'addon.cc'),
14
+ path.join(packageDir, 'src', 'ghostty_bridge.c'),
15
+ path.join(packageDir, 'src', 'ghostty_bridge.h'),
16
+ ];
17
+
18
+ function safeMtime(filePath) {
19
+ try {
20
+ return fs.statSync(filePath).mtimeMs;
21
+ } catch {
22
+ return 0;
23
+ }
24
+ }
25
+
26
+ function latestSourceMtime() {
27
+ return sourceInputs.reduce((latest, filePath) => Math.max(latest, safeMtime(filePath)), 0);
28
+ }
11
29
 
12
30
  function candidatePrebuiltPaths() {
13
31
  const candidates = [];
@@ -20,9 +38,10 @@ function candidatePrebuiltPaths() {
20
38
  return candidates;
21
39
  }
22
40
 
23
- function installPrebuiltIfPresent() {
41
+ function installPrebuiltIfPresent(minMtime = 0) {
24
42
  for (const candidate of candidatePrebuiltPaths()) {
25
43
  if (!fs.existsSync(candidate)) continue;
44
+ if (safeMtime(candidate) < minMtime) continue;
26
45
  fs.mkdirSync(outputDir, { recursive: true });
27
46
  fs.copyFileSync(candidate, outputFile);
28
47
  console.log(`[ghostty-vt-node] using prebuilt native binding from ${candidate}`);
@@ -31,12 +50,20 @@ function installPrebuiltIfPresent() {
31
50
  return false;
32
51
  }
33
52
 
34
- if (!installPrebuiltIfPresent() && process.env.ADHDEV_SKIP_GHOSTTY_VT_BUILD === '1') {
53
+ const sourceMtime = latestSourceMtime();
54
+ const outputMtime = safeMtime(outputFile);
55
+
56
+ if (outputMtime >= sourceMtime && outputMtime > 0) {
57
+ console.log(`[ghostty-vt-node] keeping existing local build at ${outputFile}`);
58
+ process.exit(0);
59
+ }
60
+
61
+ if (!installPrebuiltIfPresent(sourceMtime) && process.env.ADHDEV_SKIP_GHOSTTY_VT_BUILD === '1') {
35
62
  console.log(`[ghostty-vt-node] skipping native build for ${triplet} (ADHDEV_SKIP_GHOSTTY_VT_BUILD=1)`);
36
63
  process.exit(0);
37
64
  }
38
65
 
39
- if (installPrebuiltIfPresent()) {
66
+ if (installPrebuiltIfPresent(sourceMtime)) {
40
67
  process.exit(0);
41
68
  }
42
69
 
package/src/addon.cc CHANGED
@@ -119,6 +119,49 @@ Napi::Value FormatPlainText(const Napi::CallbackInfo& info) {
119
119
  return Napi::String::New(env, output);
120
120
  }
121
121
 
122
+ Napi::Value FormatVT(const Napi::CallbackInfo& info) {
123
+ Napi::Env env = info.Env();
124
+ TerminalBindingState* state = GetState(info);
125
+ if (!EnsureOpen(env, state)) return env.Null();
126
+
127
+ char* text = nullptr;
128
+ size_t text_len = 0;
129
+ const int result = adhdev_ghostty_terminal_format_vt(
130
+ state->terminal,
131
+ &text,
132
+ &text_len);
133
+ if (result != ADHDEV_GHOSTTY_SUCCESS) {
134
+ ThrowGhosttyError(env, "adhdev_ghostty_terminal_format_vt", result);
135
+ return env.Null();
136
+ }
137
+
138
+ std::string output(text, text_len);
139
+ adhdev_ghostty_terminal_free_text(text, text_len);
140
+ return Napi::String::New(env, output);
141
+ }
142
+
143
+ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
144
+ Napi::Env env = info.Env();
145
+ TerminalBindingState* state = GetState(info);
146
+ if (!EnsureOpen(env, state)) return env.Null();
147
+
148
+ uint16_t col = 0;
149
+ uint16_t row = 0;
150
+ const int result = adhdev_ghostty_terminal_cursor_position(
151
+ state->terminal,
152
+ &col,
153
+ &row);
154
+ if (result != ADHDEV_GHOSTTY_SUCCESS) {
155
+ ThrowGhosttyError(env, "adhdev_ghostty_terminal_cursor_position", result);
156
+ return env.Null();
157
+ }
158
+
159
+ Napi::Object output = Napi::Object::New(env);
160
+ output.Set("col", Napi::Number::New(env, col));
161
+ output.Set("row", Napi::Number::New(env, row));
162
+ return output;
163
+ }
164
+
122
165
  Napi::Value Dispose(const Napi::CallbackInfo& info) {
123
166
  TerminalBindingState* state = GetState(info);
124
167
  if (state && state->terminal) {
@@ -156,6 +199,8 @@ Napi::Value CreateTerminal(const Napi::CallbackInfo& info) {
156
199
  object.Set("write", Napi::Function::New(env, Write, "write", state));
157
200
  object.Set("resize", Napi::Function::New(env, Resize, "resize", state));
158
201
  object.Set("formatPlainText", Napi::Function::New(env, FormatPlainText, "formatPlainText", state));
202
+ object.Set("formatVT", Napi::Function::New(env, FormatVT, "formatVT", state));
203
+ object.Set("getCursorPosition", Napi::Function::New(env, GetCursorPosition, "getCursorPosition", state));
159
204
  object.Set("dispose", Napi::Function::New(env, Dispose, "dispose", state));
160
205
  return object;
161
206
  }
@@ -88,6 +88,64 @@ int adhdev_ghostty_terminal_format_plain_text(
88
88
  return GHOSTTY_SUCCESS;
89
89
  }
90
90
 
91
+ int adhdev_ghostty_terminal_format_vt(
92
+ AdhdevGhosttyTerminal* terminal,
93
+ char** out_text,
94
+ size_t* out_len) {
95
+ if (!terminal || !terminal->handle || !out_text || !out_len) return GHOSTTY_INVALID_VALUE;
96
+
97
+ GhosttyFormatterTerminalOptions options = GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions);
98
+ options.emit = GHOSTTY_FORMATTER_FORMAT_VT;
99
+ options.unwrap = false;
100
+ options.trim = false;
101
+ options.extra.palette = true;
102
+ options.extra.modes = true;
103
+ options.extra.scrolling_region = true;
104
+ options.extra.tabstops = true;
105
+ options.extra.keyboard = true;
106
+ options.extra.screen.cursor = true;
107
+ options.extra.screen.style = true;
108
+ options.extra.screen.hyperlink = true;
109
+ options.extra.screen.protection = true;
110
+ options.extra.screen.kitty_keyboard = true;
111
+ options.extra.screen.charsets = true;
112
+
113
+ GhosttyFormatter formatter = NULL;
114
+ GhosttyResult result = ghostty_formatter_terminal_new(NULL, &formatter, terminal->handle, options);
115
+ if (result != GHOSTTY_SUCCESS) return result;
116
+
117
+ uint8_t* text = NULL;
118
+ size_t text_len = 0;
119
+ result = ghostty_formatter_format_alloc(formatter, NULL, &text, &text_len);
120
+ ghostty_formatter_free(formatter);
121
+ if (result != GHOSTTY_SUCCESS) return result;
122
+
123
+ *out_text = (char*)text;
124
+ *out_len = text_len;
125
+ return GHOSTTY_SUCCESS;
126
+ }
127
+
128
+ int adhdev_ghostty_terminal_cursor_position(
129
+ AdhdevGhosttyTerminal* terminal,
130
+ uint16_t* out_col,
131
+ uint16_t* out_row) {
132
+ if (!terminal || !terminal->handle || !out_col || !out_row) return GHOSTTY_INVALID_VALUE;
133
+
134
+ GhosttyResult result = ghostty_terminal_get(
135
+ terminal->handle,
136
+ GHOSTTY_TERMINAL_DATA_CURSOR_X,
137
+ out_col);
138
+ if (result != GHOSTTY_SUCCESS) return result;
139
+
140
+ result = ghostty_terminal_get(
141
+ terminal->handle,
142
+ GHOSTTY_TERMINAL_DATA_CURSOR_Y,
143
+ out_row);
144
+ if (result != GHOSTTY_SUCCESS) return result;
145
+
146
+ return GHOSTTY_SUCCESS;
147
+ }
148
+
91
149
  void adhdev_ghostty_terminal_free_text(char* text, size_t len) {
92
150
  if (!text) return;
93
151
  ghostty_free(NULL, (uint8_t*)text, len);
@@ -35,6 +35,16 @@ int adhdev_ghostty_terminal_format_plain_text(
35
35
  char** out_text,
36
36
  size_t* out_len);
37
37
 
38
+ int adhdev_ghostty_terminal_format_vt(
39
+ AdhdevGhosttyTerminal* terminal,
40
+ char** out_text,
41
+ size_t* out_len);
42
+
43
+ int adhdev_ghostty_terminal_cursor_position(
44
+ AdhdevGhosttyTerminal* terminal,
45
+ uint16_t* out_col,
46
+ uint16_t* out_row);
47
+
38
48
  void adhdev_ghostty_terminal_free_text(char* text, size_t len);
39
49
  void adhdev_ghostty_terminal_destroy(AdhdevGhosttyTerminal* terminal);
40
50
  const char* adhdev_ghostty_result_message(int result);