Skip to content

Commit

Permalink
💥 Change rime as an addon
Browse files Browse the repository at this point in the history
  • Loading branch information
Freed-Wu committed Apr 14, 2024
1 parent f3edf88 commit 3b26c02
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 12 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ Rime input method integration of coc.nvim
## Dependencies

- [rime](https://rime.im/)
- [json-c](https://github.com/json-c/json-c)
- [pkg-config](http://pkg-config.freedesktop.org/)

```sh
# Ubuntu
sudo apt-get -y install pkg-config librime-dev libjson-c-dev librime1 libjson-c5
sudo apt-get -y install pkg-config librime-dev librime1
# ArchLinux
sudo pacman -S pkg-config librime json-c
sudo pacman -S pkg-config librime
# Android Termux
apt-get -y install pkg-config librime json-c
apt-get -y install pkg-config librime
# NixOS
# don't need to install dependencies due to nix flake
```
Expand Down
249 changes: 249 additions & 0 deletions binding.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#include <node_api.h>
#include <rime_api.h>
#include <stdio.h>

#define DEFAULT_BUFFER_SIZE 1024
#ifndef __STRING
#define __STRING(x) #x
#endif /* ifndef */
#define STRING(x) __STRING(x)
#define NODE_API_CALL(env, call) \
do { \
napi_status status = (call); \
if (status != napi_ok) { \
const napi_extended_error_info *error_info = NULL; \
napi_get_last_error_info((env), &error_info); \
const char *err_message = error_info->error_message; \
bool is_pending; \
napi_is_exception_pending((env), &is_pending); \
/* If an exception is already pending, don't rethrow it */ \
if (!is_pending) { \
const char *message = \
(err_message == NULL) ? "empty error message" : err_message; \
napi_throw_error((env), NULL, message); \
} \
return NULL; \
} \
} while (0)
#define SET_ARGV(env, argv) \
do { \
size_t argc = sizeof(argv) / sizeof((argv)[0]); \
size_t original_argc = argc; \
NODE_API_CALL(env, \
napi_get_cb_info((env), info, &argc, (argv), NULL, NULL)); \
if (argc < original_argc) { \
char str[DEFAULT_BUFFER_SIZE]; \
sprintf(str, "need %zd arguments, only get %zd arguments", \
original_argc, argc); \
napi_throw_error((env), NULL, str); \
return NULL; \
} \
} while (0)
#define SET_SESSION_ID(env, value, session_id) \
do { \
bool lossless; \
NODE_API_CALL((env), napi_get_value_bigint_uint64( \
(env), (value), &(session_id), &lossless)); \
} while (0);
#define SET_STRING(env, value, str) \
do { \
size_t size; \
NODE_API_CALL((env), \
napi_get_value_string_utf8((env), (value), (str), \
DEFAULT_BUFFER_SIZE, &size)); \
if (size == DEFAULT_BUFFER_SIZE) { \
napi_throw_error((env), NULL, \
"path length is longer than buffer size " STRING( \
DEFAULT_BUFFER_SIZE)); \
return NULL; \
} \
} while (0)

static napi_value init_rime(napi_env env, napi_callback_info info) {
napi_value result, argv[3];
char dirs[sizeof(argv) / sizeof(argv[0])][DEFAULT_BUFFER_SIZE];
SET_ARGV(env, argv);
SET_STRING(env, argv[0], dirs[0]);
SET_STRING(env, argv[1], dirs[1]);
SET_STRING(env, argv[2], dirs[2]);
RIME_STRUCT(RimeTraits, rime_traits);
rime_traits.shared_data_dir = dirs[0];
rime_traits.user_data_dir = dirs[1];
rime_traits.log_dir = dirs[2];
rime_traits.distribution_name = "Rime";
rime_traits.distribution_code_name = PROJECT_NAME;
rime_traits.distribution_version = PROJECT_VERSION;
rime_traits.app_name = "rime." PROJECT_NAME;
RimeSetup(&rime_traits);
RimeInitialize(&rime_traits);
RimeStartMaintenance(false);
RimeSessionId session_id = RimeCreateSession();
if (session_id == 0) {
napi_throw_error(env, NULL, "rime cannot create session");
return NULL;
}
NODE_API_CALL(env, napi_create_bigint_uint64(env, session_id, &result));
return result;
}

static napi_value deinit_rime(napi_env env, napi_callback_info info) {
napi_value argv[1];
RimeSessionId session_id;
SET_ARGV(env, argv);
SET_SESSION_ID(env, argv[0], session_id);
if (!RimeDestroySession(session_id)) {
char str[DEFAULT_BUFFER_SIZE];
sprintf(str, "cannot destroy session %ld", session_id);
napi_throw_error(env, NULL, str);
}
RimeFinalize();
return NULL;
}

static napi_value get_current_schema(napi_env env, napi_callback_info info) {
napi_value schema_id, argv[1];
RimeSessionId session_id;
char buffer[DEFAULT_BUFFER_SIZE];
SET_ARGV(env, argv);
SET_SESSION_ID(env, argv[0], session_id);
if (!RimeGetCurrentSchema(session_id, buffer, DEFAULT_BUFFER_SIZE)) {
napi_throw_error(env, NULL, "cannot get current schema");
return NULL;
}
NODE_API_CALL(
env, napi_create_string_utf8(env, buffer, NAPI_AUTO_LENGTH, &schema_id));
return schema_id;
}

static napi_value get_schema_list(napi_env env, napi_callback_info info) {
napi_value result, argv[1];
RimeSessionId session_id;
RimeSchemaList schema_list;
SET_ARGV(env, argv);
SET_SESSION_ID(env, argv[0], session_id);
if (!RimeGetSchemaList(&schema_list)) {
napi_throw_error(env, NULL, "cannot get schema list");
return NULL;
}
NODE_API_CALL(env,
napi_create_array_with_length(env, schema_list.size, &result));
for (size_t i = 0; i < schema_list.size; i++) {
napi_value element, schema_id, name;
RimeSchemaListItem schema_list_item = schema_list.list[i];
NODE_API_CALL(env, napi_create_object(env, &element));
NODE_API_CALL(env, napi_create_string_utf8(env, schema_list_item.schema_id,
NAPI_AUTO_LENGTH, &schema_id));
NODE_API_CALL(
env, napi_set_named_property(env, element, "schema_id", schema_id));
NODE_API_CALL(env, napi_create_string_utf8(env, schema_list_item.name,
NAPI_AUTO_LENGTH, &name));
NODE_API_CALL(env, napi_set_named_property(env, element, "name", name));
NODE_API_CALL(env, napi_set_element(env, result, i, element));
}
return result;
}

static napi_value select_schema(napi_env env, napi_callback_info info) {
napi_value argv[2];
RimeSessionId session_id;
char schema_id[DEFAULT_BUFFER_SIZE];
SET_ARGV(env, argv);
SET_SESSION_ID(env, argv[0], session_id);
SET_STRING(env, argv[1], schema_id);
if (!RimeSelectSchema(session_id, schema_id)) {
char str[DEFAULT_BUFFER_SIZE];
sprintf(str, "cannot select schema %s", schema_id);
napi_throw_error(env, NULL, str);
}
return NULL;
}

static napi_value get_context(napi_env env, napi_callback_info info) {
int modifiers;
uint32_t length;
napi_value result, argv[3];
RimeSessionId session_id;
SET_ARGV(env, argv);
SET_SESSION_ID(env, argv[0], session_id);
NODE_API_CALL(env, napi_get_array_length(env, argv[1], &length));
NODE_API_CALL(env, napi_get_value_int32(env, argv[2], &modifiers));
for (uint32_t i = 0; i < length; i++) {
int keycode;
napi_value value;
NODE_API_CALL(env, napi_get_element(env, argv[1], i, &value));
NODE_API_CALL(env, napi_get_value_int32(env, value, &keycode));
if (!RimeProcessKey(session_id, keycode, modifiers)) {
char str[DEFAULT_BUFFER_SIZE];
sprintf(str, "cannot process key %d", keycode);
napi_throw_error(env, NULL, str);
}
}
RIME_STRUCT(RimeContext, context);
if (!RimeGetContext(session_id, &context)) {
napi_throw_error(env, NULL, "cannot get context");
return NULL;
}
NODE_API_CALL(env, napi_create_object(env, &result));
if (context.composition.preedit) {
napi_value preedit;
NODE_API_CALL(env, napi_create_string_utf8(env, context.composition.preedit,
NAPI_AUTO_LENGTH, &preedit));
NODE_API_CALL(env,
napi_set_named_property(env, result, "preEdit", preedit));
}
if (context.menu.candidates) {
RimeCandidate *p_candidates[DEFAULT_BUFFER_SIZE];
size_t num_candidates = 0;
while (!context.menu.is_last_page) {
for (int i = 0; i < context.menu.num_candidates; i++)
p_candidates[num_candidates + i] = &context.menu.candidates[i];
num_candidates += context.menu.num_candidates;
// Turn to next page ...
if (!RimeProcessKey(session_id, (int)'=', 0))
napi_throw_error(env, NULL, "cannot process key =");
// and grab content.
if (!RimeGetContext(session_id, &context))
napi_throw_error(env, NULL, "cannot get context");
}
napi_value candidates;
NODE_API_CALL(
env, napi_create_array_with_length(env, num_candidates, &candidates));
NODE_API_CALL(
env, napi_set_named_property(env, result, "candidates", candidates));
for (size_t i = 0; i < num_candidates; i++) {
napi_value element, text, comment;
NODE_API_CALL(env, napi_create_object(env, &element));
NODE_API_CALL(env, napi_create_string_utf8(env, p_candidates[i]->text,
NAPI_AUTO_LENGTH, &text));
NODE_API_CALL(env, napi_set_named_property(env, element, "text", text));
if (p_candidates[i]->comment) {
NODE_API_CALL(env,
napi_create_string_utf8(env, p_candidates[i]->comment,
NAPI_AUTO_LENGTH, &comment));
NODE_API_CALL(
env, napi_set_named_property(env, element, "comment", comment));
}
NODE_API_CALL(env, napi_set_element(env, result, i, element));
}
}
RimeFreeContext(&context);
return result;
}

NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
char *names[] = {"init_rime", "deinit_rime", "get_current_schema",
"get_schema_list", "select_schema", "get_context"};
napi_callback callbacks[] = {init_rime, deinit_rime,
get_current_schema, get_schema_list,
select_schema, get_context};
napi_value functions[sizeof(names) / sizeof(names[0])];
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
NODE_API_CALL(env, napi_create_function(env, names[i], NAPI_AUTO_LENGTH,
callbacks[i], NULL, &functions[i]));

NODE_API_CALL(
env, napi_set_named_property(env, exports, names[i], functions[i]));
}

return exports;
}
9 changes: 4 additions & 5 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@
"variables": {"android_ndk_path": ""},
"targets": [
{
"target_name": "rime_cli",
"type": "executable",
"target_name": "rime",
"sources": [
"rime-cli.c",
"binding.c",
],
"defines": [
"PROJECT_NAME=\"<!(node -p \"require('./package.json').name\")\"",
"PROJECT_VERSION=\"<!(node -p \"require('./package.json').version\")\"",
],
"cflags": [
"<!@(pkg-config --cflags rime json-c)",
"<!@(pkg-config --cflags rime)",
],
"ldflags": [
"<!@(pkg-config --libs rime json-c)",
"<!@(pkg-config --libs rime)",
],
"conditions": [
[
Expand Down
1 change: 0 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
src = self;
buildInputs = [
pkg-config
json_c.dev
librime
nodejs
(
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"printWidth": 120,
"semi": true
},
"dependencies": {
"node-gyp-build": "^4.8.0"
},
"devDependencies": {
"@types/mkdirp": "^0.5.2",
"@typescript-eslint/eslint-plugin": "^4.8.2",
Expand Down
4 changes: 4 additions & 0 deletions src/binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { resolve } from 'path';
import { default as build } from 'node-gyp-build';

export default build(resolve(__dirname, '..'));
5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"compilerOptions": {
"target": "es2017",
"lib": ["es2017", "es2018"],
// BigInt literals are not available when targeting lower than ES2020.
"target": "es2020",
"lib": ["es2020"],
"module": "commonjs",
"declaration": false,
"sourceMap": true,
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ __metadata:
eslint-plugin-prettier: "npm:^3.1.4"
mkdirp: "npm:^0.5.1"
node-gyp: "npm:^10.1.0"
node-gyp-build: "npm:^4.8.0"
npm-run-all: "npm:^4.1.5"
prettier: "npm:^2.2.0"
rimraf: "npm:^5.0.5"
Expand Down Expand Up @@ -2195,6 +2196,17 @@ __metadata:
languageName: node
linkType: hard

"node-gyp-build@npm:^4.8.0":
version: 4.8.0
resolution: "node-gyp-build@npm:4.8.0"
bin:
node-gyp-build: bin.js
node-gyp-build-optional: optional.js
node-gyp-build-test: build-test.js
checksum: 10c0/85324be16f81f0235cbbc42e3eceaeb1b5ab94c8d8f5236755e1435b4908338c65a4e75f66ee343cbcb44ddf9b52a428755bec16dcd983295be4458d95c8e1ad
languageName: node
linkType: hard

"node-gyp@npm:^10.1.0":
version: 10.1.0
resolution: "node-gyp@npm:10.1.0"
Expand Down

0 comments on commit 3b26c02

Please sign in to comment.