Skip to content

Commit

Permalink
🚧 WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Freed-Wu committed Apr 17, 2024
1 parent bf27fef commit 06b5b1b
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 245 deletions.
19 changes: 0 additions & 19 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { realpath, mkdir } from 'fs/promises';
import { resolve } from 'path';
import { workspace, WorkspaceConfiguration, ExtensionContext } from 'coc.nvim';
import { exec } from 'child_process';
import { promisify } from 'util';

let execAsync = promisify(exec);

async function get_dir(...dirs: string[]): Promise<string> {
for (const dir of dirs) {
Expand Down Expand Up @@ -46,20 +41,6 @@ export class Config {
get shortcut() {
return this.cfg.get<string>('shortcut');
}
get binaryPath() {
return new Promise<string>(async (res, reject) => {
let binaryPath = await get_dir(resolve(this.context.extensionPath, 'build', 'Release', 'rime_cli'));
if (binaryPath === '') {
await execAsync(`npm rebuild`, { cwd: this.context.extensionPath });
binaryPath = await get_dir(resolve(this.context.extensionPath, 'build', 'Release', 'rime_cli'));
}
try {
res(binaryPath);
} catch (e) {
reject(e);
}
});
}
get traits() {
return new Promise<Traits>(async (res, reject) => {
let shared_data_dir = this.cfg.get<string | string[] | null>('traits.shared_data_dir');
Expand Down
8 changes: 1 addition & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@ import { Config } from './config';

export async function activate(context: ExtensionContext): Promise<void> {
const userConfig = new Config(context);
let binaryPath = await userConfig.binaryPath;
let traits = await userConfig.traits;
if (binaryPath === '') {
window.showInformationMessage(`'rime.binaryPath' cannot be found. Read README.md to know how to build it.`);
return;
}

const rimeCLI: RimeCLI = new RimeCLI(binaryPath, [traits.shared_data_dir, traits.user_data_dir, traits.log_dir]);
const rimeCLI: RimeCLI = new RimeCLI(await userConfig.traits);
rimeCLI.setCompletionStatus(userConfig.enabled);
rimeCLI.getSchema().then((schemaId) => {
if (schemaId !== userConfig.schemaId && userConfig.schemaId !== '') rimeCLI.setSchema(userConfig.schemaId);
Expand Down
10 changes: 5 additions & 5 deletions src/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ export default class SchemaList extends BasicList {
this.rimeCLI = rimeCLI;
this.addAction('open', (item: ListItem) => {
this.rimeCLI
.setSchema(item.data.schemaId)
.setSchema(item.data.schema_id)
.then((_) => {})
.catch((e) => {
console.log(`Error setting the schema: ${e}`);
window.showMessage(`Set schema ${item.data.label} failed.`);
});
this.rimeCLI
.getSchema()
.then((schemaId) => {
window.showMessage(`Changed to schema ${schemaId}.`);
.then((schema_id) => {
window.showMessage(`Changed to schema ${schema_id}.`);
})
.catch((e) => {
console.log(`Error get current schema: ${e}`);
Expand All @@ -38,8 +38,8 @@ export default class SchemaList extends BasicList {
this.rimeCLI.getSchemaList().then((res) => {
let listItems: ListItem[] = res.map((schema) => {
return {
label: schema.name + ': ' + schema.schemaId,
filterText: schema.name + schema.schemaId,
label: schema.name + ': ' + schema.schema_id,
filterText: schema.name + schema.schema_id,
data: schema,
};
});
Expand Down
250 changes: 36 additions & 214 deletions src/rime.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,42 @@
import { ChildProcess, spawn } from 'child_process';
import { ReadLine, createInterface } from 'readline';
import { Mutex } from 'async-mutex';
import fs from 'fs';

export enum RimeRequestType {
IOError,
Invalid,
Schema,
Context,
}

export interface RimeRequest {
type: RimeRequestType;
content: RimeContextRequest | RimeSchemaRequest;
}

export interface RimeComposition {
length: number;
cursorPos: number;
selStart: number;
selEnd: number;
preEdit: string;
}
import * as path from 'path';
import { exec } from 'child_process';
import { Traits } from './config';
import { default as binding } from './binding';

export interface RimeCandidate {
text: string;
comment: string;
label: number;
}

export interface RimeMenu {
pageSize: number;
pageNo: number;
isLastPage: boolean;
highlightedCandidateIndex: number;
numCandidates: number;
candidates: RimeCandidate[];
selectKeys: string;
}

export interface RimeCommit {
text: string;
comment?: string;
}

export interface RimeContext {
composition: RimeComposition;
menu: RimeMenu;
commitTextPreview: string;
selectLabels: string[];
}

export interface RimeContextRequest {
keyCode: number[];
modifiers: number;
}

export enum RimeSchemaRequestAction {
Nop,
GetCurrent,
GetList,
Select,
}

export interface RimeSchemaRequest {
action: RimeSchemaRequestAction;
schemaId: string;
preEdit?: string;
candidates: RimeCandidate[];
}

export interface RimeSchema {
schemaId: string;
schema_id: string;
name: string;
}

export class RimeCLI {
private isEnabled: boolean;
private childDead: boolean;
private binaryPath: string;
private args: string[];
private mutex: Mutex = new Mutex();
private numRestarts = 0;
private proc: ChildProcess;
private rl: ReadLine;
private isEnabled: boolean = true;
private readonly traits: Traits;
private readonly sessionId: BigInt;
private schemaList: RimeSchema[];

constructor(binaryPath: string, args?: string[]) {
this.binaryPath = binaryPath;
this.args = args ?? [];
this.childDead = false;
this.isEnabled = true;
constructor(traits: Traits) {
this.traits = traits;
try {
binding.init(traits);
} catch (e) {
exec('npm rebuild', { cwd: path.resolve(__dirname, '..') });
binding.init(traits);
}
this.sessionId = binding.createSession();
}

destroy() {
binding.destroySession(this.sessionId);
}

public async setCompletionStatus(status: boolean): Promise<boolean> {
Expand Down Expand Up @@ -120,25 +69,11 @@ export class RimeCLI {
// Group the input string into one request pack.
return new Promise<RimeContext>((resolve, reject) => {
try {
let grouped_request: RimeContextRequest = {
keyCode: [],
modifiers: 0,
};
const KEYCODE_ESCAPE = 0xff1b;
grouped_request.keyCode.push(KEYCODE_ESCAPE);
let keyCodes = [0xff1b];
for (const singleChar of input) {
grouped_request.keyCode.push(singleChar.charCodeAt(0));
keyCodes.push(singleChar.charCodeAt(0));
}
this.request({
type: RimeRequestType.Context,
content: grouped_request,
})
.then((res) => {
resolve(res);
})
.catch((e) => {
reject(e);
});
resolve(binding.getContext(this.sessionId, keyCodes, 0));
} catch (e) {
reject(e);
}
Expand All @@ -148,144 +83,31 @@ export class RimeCLI {
public async getSchemaList(): Promise<RimeSchema[]> {
return new Promise<RimeSchema[]>((resolve, reject) => {
try {
this.request({
type: RimeRequestType.Schema,
content: <RimeSchemaRequest>{
action: RimeSchemaRequestAction.GetList,
schemaId: '',
},
})
.then((res) => {
if ('schemaList' in res && res.schemaList !== null) {
this.schemaList = res.schemaList;
} else {
this.schemaList = [];
}
resolve(this.schemaList);
})
.catch((e) => {
reject(e);
});
if (this.schemaList === undefined) this.schemaList = binding.getSchemaList();
resolve(this.schemaList);
} catch (e) {
reject(`Error parse the response: ${e}`);
reject(e);
}
});
}

public async getSchema(): Promise<string> {
return new Promise<string>((resolve, reject) => {
this.request(<RimeRequest>{
type: RimeRequestType.Schema,
content: <RimeSchemaRequest>{
action: RimeSchemaRequestAction.GetCurrent,
schemaId: '',
},
})
.then((res) => {
if ('schemaId' in res && res.schemaId != null) {
resolve(res.schemaId);
} else {
reject('Invalid response');
}
})
.catch((e) => {
reject(e);
});
try {
resolve(binding.getCurrentSchema(this.sessionId));
} catch (e) {
reject(e);
}
});
}

public async setSchema(schemaId: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.request(<RimeRequest>{
type: RimeRequestType.Schema,
content: <RimeSchemaRequest>{
action: RimeSchemaRequestAction.Select,
schemaId: schemaId,
},
})
.then((res) => {
if ('success' in res && res.success == true) {
resolve();
} else {
reject(`rime-cli reports error.`);
}
})
.catch((e) => {
reject(e);
});
});
}

private async request(rimeRequest: RimeRequest): Promise<any> {
const release = await this.mutex.acquire();
try {
// Send the request asynchronously.
return await this.requestUnlocked(rimeRequest);
} finally {
release();
}
}

private async requestUnlocked(rimeRequest: RimeRequest): Promise<any> {
return new Promise<any>((resolve, reject) => {
try {
if (!this.isChildAlive()) {
this.restartChild();
}
if (!this.isChildAlive()) {
reject(new Error('rime-cli process is dead.'));
}
this.rl.once('line', (response) => {
let any_response: any = JSON.parse(response.toString());
//let candidateItems: string[] = []
if (any_response === null || any_response === undefined) {
resolve(null);
} else {
resolve(any_response);
}
});
this.proc.stdin.write(JSON.stringify(rimeRequest) + '\n', 'utf8');
resolve(binding.selectSchema(this.sessionId, schemaId));
} catch (e) {
console.log(`Error interacting with rime-cli: ${e}`);
reject(e);
}
});
}

private isChildAlive(): boolean {
return this.proc && !this.childDead;
}

private restartChild(): void {
if (this.numRestarts >= 10 || !fs.existsSync(this.binaryPath)) {
return;
}
this.numRestarts += 1;
if (this.proc) {
this.proc.kill();
}

const args = this.args;
const binaryPath = this.binaryPath;

this.proc = spawn(binaryPath, args);
this.childDead = false;
this.proc.on('exit', () => {
this.childDead = true;
});
this.proc.stdin.on('error', (error) => {
console.log(`stdin error: ${error}`);
this.childDead = true;
});
this.proc.stdout.on('error', (error) => {
// tslint:disable-next-line: no-console
console.log(`stdout error: ${error}`);
this.childDead = true;
});
this.proc.unref(); // As I understand it, this lets Node exit without waiting for the child
this.rl = createInterface({
input: this.proc.stdout,
output: this.proc.stdin,
});
}
}

0 comments on commit 06b5b1b

Please sign in to comment.