@adhdev/ghostty-vt-node 0.7.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +77 -0
- package/index.d.ts +18 -0
- package/index.js +34 -0
- package/package.json +34 -0
- package/prebuilt/darwin-arm64-node115/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node115/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node115/libghostty-vt.dylib +0 -0
- package/prebuilt/darwin-arm64-node127/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node127/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node127/libghostty-vt.dylib +0 -0
- package/prebuilt/darwin-arm64-node137/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node137/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node137/libghostty-vt.dylib +0 -0
- package/prebuilt/linux-x64-node115/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node115/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node115/libghostty-vt.so.0.1.0 +0 -0
- package/prebuilt/linux-x64-node127/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node127/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node127/libghostty-vt.so.0.1.0 +0 -0
- package/prebuilt/linux-x64-node137/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node137/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node137/libghostty-vt.so.0.1.0 +0 -0
- package/scripts/build-or-use-prebuilt.mjs +50 -0
- package/src/addon.cc +170 -0
- package/src/ghostty_bridge.c +118 -0
- package/src/ghostty_bridge.h +46 -0
package/CMakeLists.txt
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.20)
|
|
2
|
+
project(ghostty_vt_node LANGUAGES C CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
7
|
+
|
|
8
|
+
include(FetchContent)
|
|
9
|
+
|
|
10
|
+
execute_process(
|
|
11
|
+
COMMAND node -p "require('node-addon-api').include"
|
|
12
|
+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
13
|
+
OUTPUT_VARIABLE NODE_ADDON_API_DIR
|
|
14
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
15
|
+
)
|
|
16
|
+
string(REPLACE "\"" "" NODE_ADDON_API_DIR "${NODE_ADDON_API_DIR}")
|
|
17
|
+
|
|
18
|
+
set(GHOSTTY_GIT_TAG "d4a38c8661453026bd1b1bb022ba2c37b347adce" CACHE STRING "Ghostty git revision for libghostty-vt")
|
|
19
|
+
|
|
20
|
+
if(DEFINED ENV{ZIG_EXECUTABLE} AND NOT DEFINED ZIG_EXECUTABLE)
|
|
21
|
+
set(ZIG_EXECUTABLE "$ENV{ZIG_EXECUTABLE}" CACHE FILEPATH "Path to zig executable")
|
|
22
|
+
endif()
|
|
23
|
+
|
|
24
|
+
FetchContent_Declare(ghostty
|
|
25
|
+
GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git
|
|
26
|
+
GIT_TAG ${GHOSTTY_GIT_TAG}
|
|
27
|
+
)
|
|
28
|
+
FetchContent_MakeAvailable(ghostty)
|
|
29
|
+
|
|
30
|
+
add_library(${PROJECT_NAME} SHARED
|
|
31
|
+
src/addon.cc
|
|
32
|
+
src/ghostty_bridge.c
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
set(GHOSTTY_VT_RUNTIME_ALIAS "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
|
36
|
+
|
|
37
|
+
target_include_directories(${PROJECT_NAME} PRIVATE
|
|
38
|
+
${CMAKE_JS_INC}
|
|
39
|
+
${NODE_ADDON_API_DIR}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
|
43
|
+
NAPI_DISABLE_CPP_EXCEPTIONS
|
|
44
|
+
GHOSTTY_STATIC
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
target_link_libraries(${PROJECT_NAME} PRIVATE
|
|
48
|
+
${CMAKE_JS_LIB}
|
|
49
|
+
ghostty-vt
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
set_target_properties(${PROJECT_NAME} PROPERTIES
|
|
53
|
+
PREFIX ""
|
|
54
|
+
SUFFIX ".node"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if(APPLE)
|
|
58
|
+
set_target_properties(${PROJECT_NAME} PROPERTIES
|
|
59
|
+
BUILD_RPATH "@loader_path"
|
|
60
|
+
INSTALL_RPATH "@loader_path"
|
|
61
|
+
)
|
|
62
|
+
target_link_options(${PROJECT_NAME} PRIVATE "-undefined dynamic_lookup")
|
|
63
|
+
elseif(UNIX)
|
|
64
|
+
set_target_properties(${PROJECT_NAME} PROPERTIES
|
|
65
|
+
BUILD_RPATH "$ORIGIN"
|
|
66
|
+
INSTALL_RPATH "$ORIGIN"
|
|
67
|
+
)
|
|
68
|
+
endif()
|
|
69
|
+
|
|
70
|
+
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
|
71
|
+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
72
|
+
$<TARGET_FILE:ghostty-vt>
|
|
73
|
+
$<TARGET_FILE_DIR:${PROJECT_NAME}>/$<TARGET_FILE_NAME:ghostty-vt>
|
|
74
|
+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
75
|
+
$<TARGET_FILE:ghostty-vt>
|
|
76
|
+
$<TARGET_FILE_DIR:${PROJECT_NAME}>/${GHOSTTY_VT_RUNTIME_ALIAS}
|
|
77
|
+
)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type GhosttyTerminalOptions = {
|
|
2
|
+
cols: number;
|
|
3
|
+
rows: number;
|
|
4
|
+
scrollback: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type GhosttyPlainTextFormatOptions = {
|
|
8
|
+
trim?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface GhosttyTerminalHandle {
|
|
12
|
+
write(data: string | Uint8Array): void;
|
|
13
|
+
resize(cols: number, rows: number): void;
|
|
14
|
+
formatPlainText(options?: GhosttyPlainTextFormatOptions): string;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createTerminal(options: GhosttyTerminalOptions): GhosttyTerminalHandle;
|
package/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
|
|
3
|
+
function getTriplet() {
|
|
4
|
+
return `${process.platform}-${process.arch}-node${process.versions.modules}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function loadBinding() {
|
|
8
|
+
const triplet = getTriplet();
|
|
9
|
+
const explicitPrebuiltDir = process.env.ADHDEV_GHOSTTY_VT_PREBUILT_DIR
|
|
10
|
+
? path.resolve(process.env.ADHDEV_GHOSTTY_VT_PREBUILT_DIR)
|
|
11
|
+
: null;
|
|
12
|
+
const candidates = [
|
|
13
|
+
explicitPrebuiltDir ? path.join(explicitPrebuiltDir, triplet, 'ghostty_vt_node.node') : null,
|
|
14
|
+
explicitPrebuiltDir ? path.join(explicitPrebuiltDir, 'ghostty_vt_node.node') : null,
|
|
15
|
+
path.join(__dirname, 'prebuilt', triplet, 'ghostty_vt_node.node'),
|
|
16
|
+
path.join(__dirname, 'build', 'Release', 'ghostty_vt_node.node'),
|
|
17
|
+
path.join(__dirname, 'build', 'Debug', 'ghostty_vt_node.node'),
|
|
18
|
+
].filter(Boolean);
|
|
19
|
+
|
|
20
|
+
const errors = [];
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
try {
|
|
23
|
+
return require(candidate);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
errors.push(`${candidate}: ${error && error.message ? error.message : String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Unable to load @adhdev/ghostty-vt-node native binding for ${triplet}. Provide a prebuilt via ADHDEV_GHOSTTY_VT_PREBUILT_DIR or build it with "npm run build -w packages/ghostty-vt-node". ${errors.join(' | ')}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = loadBinding();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adhdev/ghostty-vt-node",
|
|
3
|
+
"version": "0.7.31",
|
|
4
|
+
"description": "Native Node binding for libghostty-vt",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"index.d.ts",
|
|
10
|
+
"prebuilt",
|
|
11
|
+
"scripts",
|
|
12
|
+
"CMakeLists.txt",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "node ./scripts/build-or-use-prebuilt.mjs",
|
|
17
|
+
"rebuild": "cmake-js rebuild",
|
|
18
|
+
"clean": "cmake-js clean"
|
|
19
|
+
},
|
|
20
|
+
"author": "vilmire",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"node-addon-api": "^8.7.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"cmake-js": "^8.0.0"
|
|
26
|
+
},
|
|
27
|
+
"license": "AGPL-3.0-or-later",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/vilmire/adhdev.git",
|
|
31
|
+
"directory": "packages/ghostty-vt-node"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/vilmire/adhdev#readme"
|
|
34
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const packageDir = path.resolve(scriptDir, '..');
|
|
8
|
+
const outputDir = path.join(packageDir, 'build', 'Release');
|
|
9
|
+
const outputFile = path.join(outputDir, 'ghostty_vt_node.node');
|
|
10
|
+
const triplet = `${process.platform}-${process.arch}-node${process.versions.modules}`;
|
|
11
|
+
|
|
12
|
+
function candidatePrebuiltPaths() {
|
|
13
|
+
const candidates = [];
|
|
14
|
+
const explicitDir = process.env.ADHDEV_GHOSTTY_VT_PREBUILT_DIR?.trim();
|
|
15
|
+
if (explicitDir) {
|
|
16
|
+
candidates.push(path.join(explicitDir, triplet, 'ghostty_vt_node.node'));
|
|
17
|
+
candidates.push(path.join(explicitDir, 'ghostty_vt_node.node'));
|
|
18
|
+
}
|
|
19
|
+
candidates.push(path.join(packageDir, 'prebuilt', triplet, 'ghostty_vt_node.node'));
|
|
20
|
+
return candidates;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function installPrebuiltIfPresent() {
|
|
24
|
+
for (const candidate of candidatePrebuiltPaths()) {
|
|
25
|
+
if (!fs.existsSync(candidate)) continue;
|
|
26
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
27
|
+
fs.copyFileSync(candidate, outputFile);
|
|
28
|
+
console.log(`[ghostty-vt-node] using prebuilt native binding from ${candidate}`);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!installPrebuiltIfPresent() && process.env.ADHDEV_SKIP_GHOSTTY_VT_BUILD === '1') {
|
|
35
|
+
console.log(`[ghostty-vt-node] skipping native build for ${triplet} (ADHDEV_SKIP_GHOSTTY_VT_BUILD=1)`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (installPrebuiltIfPresent()) {
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
44
|
+
const result = spawnSync(npx, ['cmake-js', 'compile'], {
|
|
45
|
+
cwd: packageDir,
|
|
46
|
+
env: process.env,
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
process.exit(result.status ?? 1);
|
package/src/addon.cc
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#include <algorithm>
|
|
2
|
+
#include <string>
|
|
3
|
+
|
|
4
|
+
#include <napi.h>
|
|
5
|
+
|
|
6
|
+
#include "ghostty_bridge.h"
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
struct TerminalBindingState {
|
|
11
|
+
AdhdevGhosttyTerminal* terminal = nullptr;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
std::string BuildGhosttyErrorMessage(const char* context, int result) {
|
|
15
|
+
return std::string(context) + ": " + adhdev_ghostty_result_message(result);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void ThrowGhosttyError(Napi::Env env, const char* context, int result) {
|
|
19
|
+
Napi::Error::New(env, BuildGhosttyErrorMessage(context, result)).ThrowAsJavaScriptException();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
TerminalBindingState* GetState(const Napi::CallbackInfo& info) {
|
|
23
|
+
if (!info.Data()) {
|
|
24
|
+
Napi::Error::New(info.Env(), "missing native terminal state").ThrowAsJavaScriptException();
|
|
25
|
+
return nullptr;
|
|
26
|
+
}
|
|
27
|
+
return static_cast<TerminalBindingState*>(info.Data());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
bool EnsureOpen(Napi::Env env, TerminalBindingState* state) {
|
|
31
|
+
if (!state || !state->terminal) {
|
|
32
|
+
Napi::Error::New(env, "ghostty terminal already disposed").ThrowAsJavaScriptException();
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void FinalizeState(Napi::Env, TerminalBindingState* state) {
|
|
39
|
+
if (!state) return;
|
|
40
|
+
if (state->terminal) {
|
|
41
|
+
adhdev_ghostty_terminal_destroy(state->terminal);
|
|
42
|
+
state->terminal = nullptr;
|
|
43
|
+
}
|
|
44
|
+
delete state;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Napi::Value Write(const Napi::CallbackInfo& info) {
|
|
48
|
+
Napi::Env env = info.Env();
|
|
49
|
+
TerminalBindingState* state = GetState(info);
|
|
50
|
+
if (!EnsureOpen(env, state)) return env.Undefined();
|
|
51
|
+
if (info.Length() < 1) return env.Undefined();
|
|
52
|
+
|
|
53
|
+
int result = ADHDEV_GHOSTTY_INVALID_VALUE;
|
|
54
|
+
if (info[0].IsBuffer()) {
|
|
55
|
+
Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
|
|
56
|
+
result = adhdev_ghostty_terminal_write(state->terminal, buffer.Data(), buffer.Length());
|
|
57
|
+
} else if (info[0].IsString()) {
|
|
58
|
+
std::string text = info[0].As<Napi::String>().Utf8Value();
|
|
59
|
+
result = adhdev_ghostty_terminal_write(
|
|
60
|
+
state->terminal,
|
|
61
|
+
reinterpret_cast<const uint8_t*>(text.data()),
|
|
62
|
+
text.size());
|
|
63
|
+
} else {
|
|
64
|
+
Napi::TypeError::New(env, "write expects a string or Uint8Array buffer").ThrowAsJavaScriptException();
|
|
65
|
+
return env.Undefined();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (result != ADHDEV_GHOSTTY_SUCCESS) {
|
|
69
|
+
ThrowGhosttyError(env, "adhdev_ghostty_terminal_write", result);
|
|
70
|
+
}
|
|
71
|
+
return env.Undefined();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Napi::Value Resize(const Napi::CallbackInfo& info) {
|
|
75
|
+
Napi::Env env = info.Env();
|
|
76
|
+
TerminalBindingState* state = GetState(info);
|
|
77
|
+
if (!EnsureOpen(env, state)) return env.Undefined();
|
|
78
|
+
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
|
|
79
|
+
Napi::TypeError::New(env, "resize expects cols and rows").ThrowAsJavaScriptException();
|
|
80
|
+
return env.Undefined();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const uint16_t cols = static_cast<uint16_t>(std::max(1, info[0].As<Napi::Number>().Int32Value()));
|
|
84
|
+
const uint16_t rows = static_cast<uint16_t>(std::max(1, info[1].As<Napi::Number>().Int32Value()));
|
|
85
|
+
const int result = adhdev_ghostty_terminal_resize(state->terminal, cols, rows);
|
|
86
|
+
if (result != ADHDEV_GHOSTTY_SUCCESS) {
|
|
87
|
+
ThrowGhosttyError(env, "adhdev_ghostty_terminal_resize", result);
|
|
88
|
+
}
|
|
89
|
+
return env.Undefined();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Napi::Value FormatPlainText(const Napi::CallbackInfo& info) {
|
|
93
|
+
Napi::Env env = info.Env();
|
|
94
|
+
TerminalBindingState* state = GetState(info);
|
|
95
|
+
if (!EnsureOpen(env, state)) return env.Null();
|
|
96
|
+
|
|
97
|
+
bool trim = false;
|
|
98
|
+
if (info.Length() >= 1 && info[0].IsObject()) {
|
|
99
|
+
Napi::Object options = info[0].As<Napi::Object>();
|
|
100
|
+
if (options.Has("trim")) {
|
|
101
|
+
trim = options.Get("trim").ToBoolean().Value();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
char* text = nullptr;
|
|
106
|
+
size_t text_len = 0;
|
|
107
|
+
const int result = adhdev_ghostty_terminal_format_plain_text(
|
|
108
|
+
state->terminal,
|
|
109
|
+
trim ? 1 : 0,
|
|
110
|
+
&text,
|
|
111
|
+
&text_len);
|
|
112
|
+
if (result != ADHDEV_GHOSTTY_SUCCESS) {
|
|
113
|
+
ThrowGhosttyError(env, "adhdev_ghostty_terminal_format_plain_text", result);
|
|
114
|
+
return env.Null();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
std::string output(text, text_len);
|
|
118
|
+
adhdev_ghostty_terminal_free_text(text, text_len);
|
|
119
|
+
return Napi::String::New(env, output);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Napi::Value Dispose(const Napi::CallbackInfo& info) {
|
|
123
|
+
TerminalBindingState* state = GetState(info);
|
|
124
|
+
if (state && state->terminal) {
|
|
125
|
+
adhdev_ghostty_terminal_destroy(state->terminal);
|
|
126
|
+
state->terminal = nullptr;
|
|
127
|
+
}
|
|
128
|
+
return info.Env().Undefined();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Napi::Value CreateTerminal(const Napi::CallbackInfo& info) {
|
|
132
|
+
Napi::Env env = info.Env();
|
|
133
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
134
|
+
Napi::TypeError::New(env, "options object is required").ThrowAsJavaScriptException();
|
|
135
|
+
return env.Null();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const Napi::Object options = info[0].As<Napi::Object>();
|
|
139
|
+
const uint16_t cols = static_cast<uint16_t>(std::max(1, options.Get("cols").ToNumber().Int32Value()));
|
|
140
|
+
const uint16_t rows = static_cast<uint16_t>(std::max(1, options.Get("rows").ToNumber().Int32Value()));
|
|
141
|
+
const size_t scrollback = options.Has("scrollback")
|
|
142
|
+
? static_cast<size_t>(std::max<int64_t>(0, options.Get("scrollback").ToNumber().Int64Value()))
|
|
143
|
+
: 2000;
|
|
144
|
+
|
|
145
|
+
auto* state = new TerminalBindingState();
|
|
146
|
+
const int result = adhdev_ghostty_terminal_create(cols, rows, scrollback, &state->terminal);
|
|
147
|
+
if (result != ADHDEV_GHOSTTY_SUCCESS) {
|
|
148
|
+
delete state;
|
|
149
|
+
ThrowGhosttyError(env, "adhdev_ghostty_terminal_create", result);
|
|
150
|
+
return env.Null();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Napi::Object object = Napi::Object::New(env);
|
|
154
|
+
Napi::External<TerminalBindingState> external = Napi::External<TerminalBindingState>::New(env, state, FinalizeState);
|
|
155
|
+
object.Set("_native", external);
|
|
156
|
+
object.Set("write", Napi::Function::New(env, Write, "write", state));
|
|
157
|
+
object.Set("resize", Napi::Function::New(env, Resize, "resize", state));
|
|
158
|
+
object.Set("formatPlainText", Napi::Function::New(env, FormatPlainText, "formatPlainText", state));
|
|
159
|
+
object.Set("dispose", Napi::Function::New(env, Dispose, "dispose", state));
|
|
160
|
+
return object;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
} // namespace
|
|
164
|
+
|
|
165
|
+
Napi::Object InitAddon(Napi::Env env, Napi::Object exports) {
|
|
166
|
+
exports.Set("createTerminal", Napi::Function::New(env, CreateTerminal));
|
|
167
|
+
return exports;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
NODE_API_MODULE(ghostty_vt_node, InitAddon)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#include "ghostty_bridge.h"
|
|
2
|
+
|
|
3
|
+
#include <ghostty/vt.h>
|
|
4
|
+
|
|
5
|
+
struct AdhdevGhosttyTerminal {
|
|
6
|
+
GhosttyTerminal handle;
|
|
7
|
+
uint16_t cols;
|
|
8
|
+
uint16_t rows;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
static const uint32_t kCellWidthPx = 8;
|
|
12
|
+
static const uint32_t kCellHeightPx = 16;
|
|
13
|
+
|
|
14
|
+
int adhdev_ghostty_terminal_create(
|
|
15
|
+
uint16_t cols,
|
|
16
|
+
uint16_t rows,
|
|
17
|
+
size_t scrollback,
|
|
18
|
+
AdhdevGhosttyTerminal** out_terminal) {
|
|
19
|
+
if (!out_terminal || cols == 0 || rows == 0) return GHOSTTY_INVALID_VALUE;
|
|
20
|
+
|
|
21
|
+
AdhdevGhosttyTerminal* terminal = (AdhdevGhosttyTerminal*)ghostty_alloc(NULL, sizeof(AdhdevGhosttyTerminal));
|
|
22
|
+
if (!terminal) return GHOSTTY_OUT_OF_MEMORY;
|
|
23
|
+
|
|
24
|
+
terminal->cols = cols;
|
|
25
|
+
terminal->rows = rows;
|
|
26
|
+
terminal->handle = NULL;
|
|
27
|
+
|
|
28
|
+
GhosttyTerminalOptions options = {
|
|
29
|
+
.cols = cols,
|
|
30
|
+
.rows = rows,
|
|
31
|
+
.max_scrollback = scrollback,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
GhosttyResult result = ghostty_terminal_new(NULL, &terminal->handle, options);
|
|
35
|
+
if (result != GHOSTTY_SUCCESS) {
|
|
36
|
+
ghostty_free(NULL, (uint8_t*)terminal, sizeof(AdhdevGhosttyTerminal));
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
*out_terminal = terminal;
|
|
41
|
+
return GHOSTTY_SUCCESS;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
int adhdev_ghostty_terminal_write(
|
|
45
|
+
AdhdevGhosttyTerminal* terminal,
|
|
46
|
+
const uint8_t* data,
|
|
47
|
+
size_t len) {
|
|
48
|
+
if (!terminal || !terminal->handle || (!data && len > 0)) return GHOSTTY_INVALID_VALUE;
|
|
49
|
+
ghostty_terminal_vt_write(terminal->handle, data, len);
|
|
50
|
+
return GHOSTTY_SUCCESS;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
int adhdev_ghostty_terminal_resize(
|
|
54
|
+
AdhdevGhosttyTerminal* terminal,
|
|
55
|
+
uint16_t cols,
|
|
56
|
+
uint16_t rows) {
|
|
57
|
+
if (!terminal || !terminal->handle || cols == 0 || rows == 0) return GHOSTTY_INVALID_VALUE;
|
|
58
|
+
terminal->cols = cols;
|
|
59
|
+
terminal->rows = rows;
|
|
60
|
+
(void)kCellWidthPx;
|
|
61
|
+
(void)kCellHeightPx;
|
|
62
|
+
return ghostty_terminal_resize(terminal->handle, cols, rows);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int adhdev_ghostty_terminal_format_plain_text(
|
|
66
|
+
AdhdevGhosttyTerminal* terminal,
|
|
67
|
+
int trim,
|
|
68
|
+
char** out_text,
|
|
69
|
+
size_t* out_len) {
|
|
70
|
+
if (!terminal || !terminal->handle || !out_text || !out_len) return GHOSTTY_INVALID_VALUE;
|
|
71
|
+
|
|
72
|
+
GhosttyFormatterTerminalOptions options = GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions);
|
|
73
|
+
options.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
|
|
74
|
+
options.trim = trim != 0;
|
|
75
|
+
|
|
76
|
+
GhosttyFormatter formatter = NULL;
|
|
77
|
+
GhosttyResult result = ghostty_formatter_terminal_new(NULL, &formatter, terminal->handle, options);
|
|
78
|
+
if (result != GHOSTTY_SUCCESS) return result;
|
|
79
|
+
|
|
80
|
+
uint8_t* text = NULL;
|
|
81
|
+
size_t text_len = 0;
|
|
82
|
+
result = ghostty_formatter_format_alloc(formatter, NULL, &text, &text_len);
|
|
83
|
+
ghostty_formatter_free(formatter);
|
|
84
|
+
if (result != GHOSTTY_SUCCESS) return result;
|
|
85
|
+
|
|
86
|
+
*out_text = (char*)text;
|
|
87
|
+
*out_len = text_len;
|
|
88
|
+
return GHOSTTY_SUCCESS;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
void adhdev_ghostty_terminal_free_text(char* text, size_t len) {
|
|
92
|
+
if (!text) return;
|
|
93
|
+
ghostty_free(NULL, (uint8_t*)text, len);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void adhdev_ghostty_terminal_destroy(AdhdevGhosttyTerminal* terminal) {
|
|
97
|
+
if (!terminal) return;
|
|
98
|
+
if (terminal->handle) {
|
|
99
|
+
ghostty_terminal_free(terminal->handle);
|
|
100
|
+
terminal->handle = NULL;
|
|
101
|
+
}
|
|
102
|
+
ghostty_free(NULL, (uint8_t*)terminal, sizeof(AdhdevGhosttyTerminal));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const char* adhdev_ghostty_result_message(int result) {
|
|
106
|
+
switch (result) {
|
|
107
|
+
case GHOSTTY_SUCCESS:
|
|
108
|
+
return "success";
|
|
109
|
+
case GHOSTTY_OUT_OF_MEMORY:
|
|
110
|
+
return "out of memory";
|
|
111
|
+
case GHOSTTY_INVALID_VALUE:
|
|
112
|
+
return "invalid value";
|
|
113
|
+
case GHOSTTY_OUT_OF_SPACE:
|
|
114
|
+
return "out of space";
|
|
115
|
+
default:
|
|
116
|
+
return "unknown error";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#ifndef ADHDEV_GHOSTTY_BRIDGE_H
|
|
2
|
+
#define ADHDEV_GHOSTTY_BRIDGE_H
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
#ifdef __cplusplus
|
|
8
|
+
extern "C" {
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
typedef struct AdhdevGhosttyTerminal AdhdevGhosttyTerminal;
|
|
12
|
+
|
|
13
|
+
#define ADHDEV_GHOSTTY_SUCCESS 0
|
|
14
|
+
#define ADHDEV_GHOSTTY_INVALID_VALUE -2
|
|
15
|
+
|
|
16
|
+
int adhdev_ghostty_terminal_create(
|
|
17
|
+
uint16_t cols,
|
|
18
|
+
uint16_t rows,
|
|
19
|
+
size_t scrollback,
|
|
20
|
+
AdhdevGhosttyTerminal** out_terminal);
|
|
21
|
+
|
|
22
|
+
int adhdev_ghostty_terminal_write(
|
|
23
|
+
AdhdevGhosttyTerminal* terminal,
|
|
24
|
+
const uint8_t* data,
|
|
25
|
+
size_t len);
|
|
26
|
+
|
|
27
|
+
int adhdev_ghostty_terminal_resize(
|
|
28
|
+
AdhdevGhosttyTerminal* terminal,
|
|
29
|
+
uint16_t cols,
|
|
30
|
+
uint16_t rows);
|
|
31
|
+
|
|
32
|
+
int adhdev_ghostty_terminal_format_plain_text(
|
|
33
|
+
AdhdevGhosttyTerminal* terminal,
|
|
34
|
+
int trim,
|
|
35
|
+
char** out_text,
|
|
36
|
+
size_t* out_len);
|
|
37
|
+
|
|
38
|
+
void adhdev_ghostty_terminal_free_text(char* text, size_t len);
|
|
39
|
+
void adhdev_ghostty_terminal_destroy(AdhdevGhosttyTerminal* terminal);
|
|
40
|
+
const char* adhdev_ghostty_result_message(int result);
|
|
41
|
+
|
|
42
|
+
#ifdef __cplusplus
|
|
43
|
+
}
|
|
44
|
+
#endif
|
|
45
|
+
|
|
46
|
+
#endif
|