Skip to content

Commit

Permalink
feat: experimental TCP support
Browse files Browse the repository at this point in the history
- refactor(rime): global rime instance with once_cell
- feat: support serving remote LSP client via TCP
  • Loading branch information
wlh320 committed Feb 8, 2023
1 parent a4f4211 commit 07882ba
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ librime-sys = { version = "0.1.0", git = "https://github.com/lotem/librime-sys"
dashmap = "5.4.0"
regex = "1.7.1"
ropey = "1.5.1"
tokio = { version = "1.17", features = ["io-util", "io-std", "macros", "rt-multi-thread"] }
tokio = { version = "1.17", features = ["io-util", "io-std", "macros", "rt-multi-thread", "net"] }
tower-lsp = "0.17.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.91"
directories = "4.0.1"
lazy_static = "1.4.0"
ouroboros = "0.15.5"
thiserror = "1.0.38"
once_cell = "1.17.0"

[features]
default = []
Expand Down
10 changes: 5 additions & 5 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// consts
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use regex::Regex;

pub const NT_PTN: &str = r"((?P<py>[a-zA-Z[:punct:]]+)(?P<se>[0-9]?))$";
Expand All @@ -12,11 +12,11 @@ macro_rules! trigger_ptn {
}
pub(crate) use trigger_ptn;

lazy_static! {
pub static ref NT_RE: Regex = Regex::new(NT_PTN).unwrap(); // no trigger
pub static ref AUTO_TRIGGER_RE: Regex = Regex::new(AUTO_TRIGGER_PTN).unwrap(); // no trigger
}
// regex
pub static NT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(NT_PTN).unwrap());
pub static AUTO_TRIGGER_RE: Lazy<Regex> = Lazy::new(|| Regex::new(AUTO_TRIGGER_PTN).unwrap());

// keycodes
pub const K_BACKSPACE: i32 = 0xff08;
pub const K_PGUP: i32 = 0xff55;
pub const K_PGDN: i32 = 0xff56;
2 changes: 1 addition & 1 deletion src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ impl InputState {
&self,
new_offset: usize,
new_input: &Input,
rime: &Rime,
) -> InputResult {
let rime = Rime::global();
// new typing
if self.offset != new_offset {
rime.destroy_session(self.session_id);
Expand Down
23 changes: 11 additions & 12 deletions src/lsp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::config::{Config, Settings};
use crate::consts::{trigger_ptn, NT_RE};
use crate::input::{Input, InputResult, InputState};
use crate::rime::{Candidate, Rime, RimeError, RimeResponse};
use crate::rime::{Candidate, Rime, RimeError, RimeResponse, RIME};
use crate::utils;
use dashmap::DashMap;
use regex::Regex;
Expand All @@ -14,7 +14,6 @@ use tower_lsp::{Client, LanguageServer};

pub struct Backend {
client: Client,
rime: Rime,
documents: DashMap<String, Rope>,
state: DashMap<String, Option<InputState>>,
config: RwLock<Config>,
Expand All @@ -25,7 +24,6 @@ impl Backend {
pub fn new(client: Client) -> Backend {
Backend {
client,
rime: Rime::new(),
documents: DashMap::new(),
state: DashMap::new(),
config: RwLock::new(Config::default()),
Expand All @@ -47,7 +45,11 @@ impl Backend {
let trigger_characters = &config.trigger_characters;
self.compile_regex(trigger_characters).await;
// init rime
self.rime.init(shared_data_dir, user_data_dir, log_dir)
if RIME.get().is_none() {
let rime = Rime::init(shared_data_dir, user_data_dir, log_dir)?;
RIME.set(rime).ok();
}
Ok(())
}

async fn on_change(&self, params: TextDocumentItem) {
Expand Down Expand Up @@ -134,6 +136,7 @@ impl Backend {
async fn get_completions(&self, uri: Url, position: Position) -> Option<Vec<CompletionItem>> {
let max_candidates = self.config.read().await.max_candidates;
let is_trigger_set = !self.config.read().await.trigger_characters.is_empty();
let rime = Rime::global();

// get new input
let rope = self.documents.get(&uri.to_string())?;
Expand All @@ -156,14 +159,14 @@ impl Backend {
// handle new input
let mut last_state = self.state.entry(uri.to_string()).or_default();
let InputResult { is_new, select } = match (*last_state).as_ref() {
Some(state) => state.handle_new_input(new_offset, &new_input, &self.rime),
Some(state) => state.handle_new_input(new_offset, &new_input),
None => InputResult::default(),
};

// get rime session_id
let session_id = if is_new {
let bytes = new_input.borrow_pinyin().as_bytes();
self.rime.new_session_with_keys(bytes).ok()?
rime.new_session_with_keys(bytes).ok()?
} else {
(*last_state).as_ref().map(|s| s.session_id).unwrap()
};
Expand All @@ -172,10 +175,7 @@ impl Backend {
let RimeResponse {
preedit,
candidates,
} = match self
.rime
.get_response_from_session(session_id, max_candidates)
{
} = match rime.get_response_from_session(session_id, max_candidates) {
Ok(r) => r,
Err(e) => {
self.client.log_message(MessageType::ERROR, e).await;
Expand Down Expand Up @@ -279,7 +279,6 @@ impl LanguageServer for Backend {
}

async fn shutdown(&self) -> Result<()> {
self.rime.destroy();
Ok(())
}

Expand Down Expand Up @@ -377,7 +376,7 @@ impl LanguageServer for Backend {
"rime-ls.sync-user-data" => {
self.notify_work_begin(token.clone(), command).await;
// TODO: do it in async way.
self.rime.sync_user_data();
Rime::global().sync_user_data();
self.notify_work_done(token.clone(), "Rime is Ready.").await;
}
_ => {
Expand Down
44 changes: 41 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
use std::{net::SocketAddr, str::FromStr};

use rime_ls::lsp::Backend;
use tokio::net::{TcpListener, TcpStream};
use tower_lsp::{LspService, Server};

#[tokio::main]
async fn main() {
let (service, socket) = LspService::build(Backend::new).finish();
async fn run_stdio() {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();

let (service, socket) = LspService::build(Backend::new).finish();
Server::new(stdin, stdout, socket).serve(service).await;
}

async fn run_tcp(stream: TcpStream) {
let (read, write) = tokio::io::split(stream);

let (service, socket) = LspService::build(Backend::new).finish();
Server::new(read, write, socket).serve(service).await;
}

async fn run_tcp_forever(bind_addr: SocketAddr) -> tokio::io::Result<()> {
println!("Listening on: {}", &bind_addr);
let listener = TcpListener::bind(bind_addr).await?;
loop {
let (stream, _) = listener.accept().await?;
tokio::spawn(run_tcp(stream));
}
}

fn usage() {
println!("rime_ls v{}", env!("CARGO_PKG_VERSION"));
println!("Usage: rime_ls [--listen <bind_addr>]")
}

#[tokio::main]
async fn main() {
let mut args = std::env::args();
match args.nth(1).as_deref() {
None => run_stdio().await,
Some("--listen") => {
let addr = args.next().unwrap_or("127.0.0.1:9257".to_owned());
let addr = SocketAddr::from_str(&addr).unwrap();
run_tcp_forever(addr).await.unwrap();
}
_ => usage(),
}
}
45 changes: 20 additions & 25 deletions src/rime.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::consts::{K_PGDN, K_PGUP};
use librime_sys as librime;
use once_cell::sync::OnceCell;
use std::ffi::{CStr, CString, NulError};
use std::sync::RwLock;
use thiserror::Error;
Expand All @@ -13,12 +14,13 @@ macro_rules! rime_struct_init {
}};
}

/// global rime instance
pub static RIME: OnceCell<Rime> = OnceCell::new();

/// just call unsafe c ffi function simply
/// TODO: make a good rust wrapper
#[derive(Debug)]
pub struct Rime {
is_init: RwLock<bool>,
}
pub struct Rime;

#[derive(Debug)]
pub struct Candidate {
Expand All @@ -32,7 +34,7 @@ pub struct Candidate {
pub enum RimeError {
#[error("null pointer when talking with librime")]
NullPointer(#[from] NulError),
#[error("fail to get candidates")]
#[error("failed to get candidates")]
GetCandidatesFailed,
#[error("session {0} not found")]
SessionNotFound(usize),
Expand All @@ -46,29 +48,25 @@ pub struct RimeResponse {
pub candidates: Vec<Candidate>,
}

impl Rime {
pub fn new() -> Self {
Rime {
is_init: RwLock::new(false),
}
impl Drop for Rime {
fn drop(&mut self) {
// FIXME: it seems that staic variables will not be dorpped?
self.destroy();
println!("rime exit");
}
}

#[allow(dead_code)]
pub fn version() -> Option<&'static str> {
unsafe {
let api = librime::rime_get_api();
(*api)
.get_version
.and_then(|f| CStr::from_ptr(f()).to_str().ok())
}
impl Rime {
/// get global rime instance
pub fn global() -> &'static Rime {
RIME.get().expect("Rime is not initialized")
}

pub fn init(
&self,
shared_data_dir: &str,
user_data_dir: &str,
log_dir: &str,
) -> Result<(), RimeError> {
) -> Result<Self, RimeError> {
let mut traits = rime_struct_init!(librime::RimeTraits);

// set dirs
Expand Down Expand Up @@ -96,13 +94,11 @@ impl Rime {
librime::RimeJoinMaintenanceThread();
}
}

*self.is_init.write().unwrap() = true;
Ok(())
Ok(Rime)
}

pub fn destroy(&self) {
if *self.is_init.read().unwrap() {
if RIME.get().is_some() {
unsafe {
librime::RimeFinalize();
}
Expand Down Expand Up @@ -289,8 +285,7 @@ fn test_get_candidates() {
let log_dir = "/tmp";

// init
let rime = Rime::new();
rime.init(shared_data_dir, user_data_dir, log_dir).unwrap();
let rime = Rime::init(shared_data_dir, user_data_dir, log_dir).unwrap();
// simulate typing
let max_candidates = 10;
let keys = vec![b'w', b'l', b'h'];
Expand Down

0 comments on commit 07882ba

Please sign in to comment.