diff --git a/Cargo.lock b/Cargo.lock index 70cb23cd..54663954 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1339,6 +1339,15 @@ dependencies = [ "libc", ] +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "humantime" version = "1.3.0" @@ -1610,6 +1619,7 @@ dependencies = [ "git2", "include_dir", "jsonrpc-lite", + "lapce-proxy", "lazy_static 1.4.0", "lsp-types 0.82.0", "openssh", @@ -1627,6 +1637,7 @@ dependencies = [ "xi-core-lib", "xi-rope", "xi-rpc", + "xi-trace", ] [[package]] @@ -1648,8 +1659,11 @@ dependencies = [ name = "lapce-proxy" version = "0.0.1" dependencies = [ + "anyhow", + "home", "serde", "serde_json", + "toml", "xi-rope", "xi-rpc", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index 9bb20fc5..d4137262 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Dongdong Zhou "] edition = "2018" [dependencies] +lapce-proxy = { path = "../proxy" } +xi-trace = { path = "../../xi-editor/rust/trace/" } git2 = "0.13" openssh = "0.7.0" tokio = { version = "0.3.3", features = ["full"] } diff --git a/core/src/buffer.rs b/core/src/buffer.rs index eb587ac7..4bf8dec5 100644 --- a/core/src/buffer.rs +++ b/core/src/buffer.rs @@ -110,11 +110,21 @@ pub fn new( event_sink: ExtEventSink, sender: Sender<(WindowId, WidgetId, BufferId, u64)>, ) -> Buffer { - let rope = if let Ok(rope) = load_file(&window_id, &tab_id, path) { - rope - } else { - Rope::from("") - }; + let state = LAPCE_APP_STATE.get_tab_state(&window_id, &tab_id); + let content = state + .proxy + .lock() + .as_ref() + .unwrap() + .new_buffer(buffer_id, PathBuf::from(path.to_string())) + .unwrap(); + let rope = Rope::from_str(&content).unwrap(); + + // let rope = if let Ok(rope) = load_file(&window_id, &tab_id, path) { + // rope + // } else { + // Rope::from("") + // }; let mut parser = new_parser(LapceLanguage::Rust); let tree = parser.parse(&rope.to_string(), None).unwrap(); @@ -150,7 +160,6 @@ pub fn new( let language_id = buffer.language_id.clone(); - let state = LAPCE_APP_STATE.get_tab_state(&window_id, &tab_id); state.plugins.lock().new_buffer(&PluginBufferInfo { buffer_id: buffer_id.clone(), language_id: buffer.language_id.clone(), diff --git a/core/src/editor.rs b/core/src/editor.rs index 649dec6f..71cd795c 100644 --- a/core/src/editor.rs +++ b/core/src/editor.rs @@ -3050,7 +3050,8 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceUIState, env: &Env) { }; let x = (last_line.to_string().len() - content.to_string().len()) as f64 - * width; + * width + + 10.0; let content = content.to_string(); if let Some(text_layout) = self.text_layouts.get_mut(&content) { if text_layout.text != content { @@ -3294,8 +3295,11 @@ fn paint_selection( &Mode::Visual => (), _ => return, } - let start = buffer.offset_of_line(start_line); let last_line = buffer.last_line(); + if start_line > last_line { + return; + } + let start = buffer.offset_of_line(start_line); let mut end_line = start_line + number_lines; if end_line > last_line { end_line = last_line; diff --git a/core/src/state.rs b/core/src/state.rs index 047cfcea..a0aec167 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -25,15 +25,26 @@ }; use git2::Oid; use git2::Repository; +use lapce_proxy::dispatch::NewBufferResponse; use lazy_static::lazy_static; use parking_lot::Mutex; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::json; +use std::io::BufReader; use std::path::Path; +use std::process::Child; +use std::process::Command; +use std::process::Stdio; use std::sync::mpsc::{channel, Receiver, Sender}; use std::{ collections::HashMap, fs::File, io::Read, path::PathBuf, str::FromStr, sync::Arc, thread, }; use toml; +use xi_rpc::Handler; +use xi_rpc::RpcLoop; +use xi_rpc::RpcPeer; +use xi_trace::enable_tracing; lazy_static! { //pub static ref LAPCE_STATE: LapceState = LapceState::new(); @@ -290,30 +301,11 @@ pub struct LapceTabState { pub plugins: Arc>, pub lsp: Arc>, pub ssh_session: Arc>>, + pub proxy: Arc>>, } impl LapceTabState { pub fn new(window_id: WindowId) -> LapceTabState { - let repo = Repository::open("/Users/Lulu/lapce").unwrap(); - let head = repo.head().unwrap(); - let tree = head.peel_to_tree().unwrap(); - // let tree = repo.find_tree(oid).unwrap(); - let tree_entry = tree.get_path(&PathBuf::from("Cargo.toml")).unwrap(); - let blob = repo.find_blob(tree_entry.id()).unwrap(); - let mut patch = git2::Patch::from_blob_and_buffer( - &blob, - None, - "lsjdf".as_bytes(), - None, - None, - ) - .unwrap(); - for i in 0..patch.num_hunks() { - let hunk = patch.hunk(i).unwrap(); - println!("hunk {:?}", hunk); - } - println!("diff {}", patch.to_buf().unwrap().as_str().unwrap()); - let workspace = LapceWorkspace { kind: LapceWorkspaceType::Local, path: PathBuf::from("/Users/Lulu/lapce"), @@ -352,7 +344,9 @@ pub fn new(window_id: WindowId) -> LapceTabState { tab_id.clone(), ))), ssh_session: Arc::new(Mutex::new(None)), + proxy: Arc::new(Mutex::new(None)), }; + start_proxy_process(window_id, tab_id); let local_state = state.clone(); thread::spawn(move || { local_state.start_plugin(); @@ -582,6 +576,89 @@ pub fn set_container(&mut self, container: WidgetId) { } } +pub struct LapceProxy { + peer: RpcPeer, + process: Child, +} + +impl LapceProxy { + pub fn new_buffer(&self, buffer_id: BufferId, path: PathBuf) -> Result { + enable_tracing(); + let result = self + .peer + .send_rpc_request( + "new_buffer", + &json!({ "buffer_id": buffer_id, "path": path }), + ) + .map_err(|e| anyhow!("{:?}", e))?; + + let resp: NewBufferResponse = serde_json::from_value(result)?; + return Ok(resp.content); + } +} + +fn start_proxy_process(window_id: WindowId, tab_id: WidgetId) { + thread::spawn(move || { + let child = Command::new("/Users/Lulu/lapce/target/debug/lapce-proxy") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn(); + if child.is_err() { + println!("can't start proxy {:?}", child); + return; + } + let mut child = child.unwrap(); + let child_stdin = child.stdin.take().unwrap(); + let child_stdout = child.stdout.take().unwrap(); + let mut looper = RpcLoop::new(child_stdin); + let peer: RpcPeer = Box::new(looper.get_raw_peer()); + let proxy = LapceProxy { + peer, + process: child, + }; + { + let state = LAPCE_APP_STATE.get_tab_state(&window_id, &tab_id); + *state.proxy.lock() = Some(proxy); + } + + let mut handler = ProxyHandler {}; + if let Err(e) = + looper.mainloop(|| BufReader::new(child_stdout), &mut handler) + { + println!("proxy main loop failed {:?}", e); + } + println!("proxy main loop exit"); + }); +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Notification {} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Request {} + +pub struct ProxyHandler {} + +impl Handler for ProxyHandler { + type Notification = Notification; + type Request = Request; + + fn handle_notification( + &mut self, + ctx: &xi_rpc::RpcCtx, + rpc: Self::Notification, + ) { + } + + fn handle_request( + &mut self, + ctx: &xi_rpc::RpcCtx, + rpc: Self::Request, + ) -> Result { + Err(xi_rpc::RemoteError::InvalidRequest(None)) + } +} + pub fn hex_to_color(hex: &str) -> Result { let hex = hex.trim_start_matches("#"); let (r, g, b, a) = match hex.len() { diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index f27e5755..7e732db9 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -9,3 +9,6 @@ xi-rpc = { path = "../../xi-editor/rust/rpc/" } xi-rope = { path = "../../xi-editor/rust/rope/" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.59" +anyhow = "1.0.32" +home = "0.5.3" +toml = "0.5.6" diff --git a/proxy/src/buffer.rs b/proxy/src/buffer.rs index 59d6917b..4d6c417c 100644 --- a/proxy/src/buffer.rs +++ b/proxy/src/buffer.rs @@ -1,3 +1,8 @@ +use anyhow::Result; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; + use serde::{Deserialize, Deserializer, Serialize}; use xi_rope::{ interval::IntervalBounds, rope::Rope, Cursor, Delta, DeltaBuilder, Interval, @@ -10,4 +15,23 @@ pub struct Buffer { pub id: BufferId, pub rope: Rope, + pub path: PathBuf, +} + +impl Buffer { + pub fn new(id: BufferId, path: PathBuf) -> Buffer { + let rope = if let Ok(rope) = load_file(&path) { + rope + } else { + Rope::from("") + }; + Buffer { id, rope, path } + } +} + +fn load_file(path: &PathBuf) -> Result { + let mut f = File::open(path)?; + let mut bytes = Vec::new(); + f.read_to_end(&mut bytes)?; + Ok(Rope::from(std::str::from_utf8(&bytes)?)) } diff --git a/proxy/src/core_proxy.rs b/proxy/src/core_proxy.rs new file mode 100644 index 00000000..7fb11308 --- /dev/null +++ b/proxy/src/core_proxy.rs @@ -0,0 +1,12 @@ +use xi_rpc::RpcPeer; + +#[derive(Clone)] +pub struct CoreProxy { + pub peer: RpcPeer, +} + +impl CoreProxy { + pub fn new(peer: RpcPeer) -> Self { + Self { peer } + } +} diff --git a/proxy/src/dispatch.rs b/proxy/src/dispatch.rs index 1b0f94a9..e96e6db9 100644 --- a/proxy/src/dispatch.rs +++ b/proxy/src/dispatch.rs @@ -1,23 +1,47 @@ use crate::buffer::{Buffer, BufferId}; +use crate::core_proxy::CoreProxy; +use crate::plugin::PluginCatalog; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; +use xi_rpc::RpcPeer; use xi_rpc::{Handler, RpcCtx}; pub struct Dispatcher { + peer: RpcPeer, buffers: HashMap, + plugins: Arc>, } #[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "method", content = "params")] pub enum Notification {} #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Request {} +#[serde(rename_all = "snake_case")] +#[serde(tag = "method", content = "params")] +pub enum Request { + NewBuffer { buffer_id: BufferId, path: PathBuf }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NewBufferResponse { + pub content: String, +} impl Dispatcher { - pub fn new() -> Dispatcher { + pub fn new(peer: RpcPeer) -> Dispatcher { + let mut plugins = PluginCatalog::new(); + plugins.reload(); + plugins.start_all(CoreProxy::new(peer.clone())); Dispatcher { + peer, buffers: HashMap::new(), + plugins: Arc::new(Mutex::new(plugins)), } } } @@ -33,6 +57,15 @@ fn handle_request( ctx: &RpcCtx, rpc: Self::Request, ) -> Result { + match rpc { + Request::NewBuffer { buffer_id, path } => { + let buffer = Buffer::new(buffer_id, path); + let content = buffer.rope.to_string(); + self.buffers.insert(buffer_id, buffer); + let resp = NewBufferResponse { content }; + return Ok(serde_json::to_value(resp).unwrap()); + } + } Err(xi_rpc::RemoteError::InvalidRequest(None)) } } diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index e3d26b4b..017176a3 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -1,16 +1,19 @@ pub mod buffer; +pub mod core_proxy; pub mod dispatch; pub mod plugin; use dispatch::Dispatcher; -use xi_rpc::RpcLoop; use std::io; +use xi_rpc::RpcLoop; +use xi_rpc::RpcPeer; pub fn mainloop() { let stdin = io::stdin(); let stdout = io::stdout(); let mut rpc_looper = RpcLoop::new(stdout); - let mut dispatcher = Dispatcher::new(); + let peer: RpcPeer = Box::new(rpc_looper.get_raw_peer()); + let mut dispatcher = Dispatcher::new(peer); let result = rpc_looper.mainloop(|| stdin.lock(), &mut dispatcher); eprintln!("rpc looper stopped {:?}", result); } diff --git a/proxy/src/plugin.rs b/proxy/src/plugin.rs index 24e8cfa1..336c3f8b 100644 --- a/proxy/src/plugin.rs +++ b/proxy/src/plugin.rs @@ -1,30 +1,241 @@ +use anyhow::Result; +use home::home_dir; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use serde_json::Value; use std::collections::HashMap; +use std::fs; +use std::io::BufReader; +use std::io::Read; use std::path::PathBuf; +use std::process::Child; +use std::process::Command; +use std::process::Stdio; +use std::sync::Arc; +use std::thread; +use toml; +use xi_rpc::Handler; +use xi_rpc::RpcLoop; +use xi_rpc::RpcPeer; + +use crate::buffer::BufferId; +use crate::core_proxy::CoreProxy; pub type PluginName = String; +#[derive(Clone, Debug, Default)] +pub struct Counter(usize); + +impl Counter { + pub fn next(&mut self) -> usize { + let n = self.0; + self.0 = n + 1; + n + 1 + } +} + +#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize)] +pub struct PluginId(pub usize); + +#[derive(Deserialize, Clone)] pub struct PluginDescription { pub name: String, pub version: String, pub exec_path: PathBuf, + dir: PathBuf, +} + +pub struct Plugin { + id: PluginId, + core: CoreProxy, + peer: RpcPeer, + name: String, + process: Child, } pub struct PluginCatalog { + id_counter: Counter, items: HashMap, } impl PluginCatalog { pub fn new() -> PluginCatalog { PluginCatalog { + id_counter: Counter::default(), items: HashMap::new(), } } pub fn reload(&mut self) { - println!("plugin reload from paths"); + eprintln!("plugin reload from paths"); self.items.clear(); self.load(); } - pub fn load(&mut self) {} + pub fn load(&mut self) { + let all_manifests = find_all_manifests(); + for manifest_path in &all_manifests { + match load_manifest(manifest_path) { + Err(e) => (), + Ok(manifest) => { + self.items.insert(manifest.name.clone(), manifest); + } + } + } + } + + pub fn start_all(&mut self, core: CoreProxy) { + for (_, manifest) in self.items.clone().iter() { + start_plugin_process( + self.next_plugin_id(), + core.clone(), + manifest.clone(), + ); + } + } + + pub fn next_plugin_id(&mut self) -> PluginId { + PluginId(self.id_counter.next()) + } +} + +fn start_plugin_process( + id: PluginId, + core: CoreProxy, + plugin_desc: PluginDescription, +) { + thread::spawn(move || { + let parts: Vec<&str> = plugin_desc + .exec_path + .to_str() + .unwrap() + .split(" ") + .into_iter() + .collect(); + let mut child = Command::new(parts[0]); + for part in &parts[1..] { + child.arg(part); + } + child.current_dir(&plugin_desc.dir); + let child = child.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn(); + if child.is_err() { + eprintln!("can't start proxy {:?}", child); + return; + } + let mut child = child.unwrap(); + let child_stdin = child.stdin.take().unwrap(); + let child_stdout = child.stdout.take().unwrap(); + let mut looper = RpcLoop::new(child_stdin); + let peer: RpcPeer = Box::new(looper.get_raw_peer()); + let name = plugin_desc.name.clone(); + let plugin = Plugin { + id, + core: core.clone(), + peer, + process: child, + name, + }; + eprintln!("plugin main loop starting {:?}", &plugin_desc.exec_path); + plugin.initialize(); + let mut handler = PluginHandler { core }; + if let Err(e) = + looper.mainloop(|| BufReader::new(child_stdout), &mut handler) + { + eprintln!("plugin main loop failed {} {:?}", e, &plugin_desc.dir); + } + eprintln!("plugin main loop exit {:?}", plugin_desc.dir); + }); +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "method", content = "params")] +pub enum PluginNotification { + StartLspServer { + exec_path: String, + language_id: String, + options: Option, + }, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum PluginRequest {} + +pub struct PluginHandler { + core: CoreProxy, +} + +impl Handler for PluginHandler { + type Notification = PluginNotification; + type Request = PluginRequest; + + fn handle_notification( + &mut self, + ctx: &xi_rpc::RpcCtx, + rpc: Self::Notification, + ) { + match &rpc { + PluginNotification::StartLspServer { + exec_path, + language_id, + options, + } => { + self.core.peer.send_rpc_notification( + "start_lsp_server", + &serde_json::to_value(&rpc).unwrap(), + ); + } + } + } + + fn handle_request( + &mut self, + ctx: &xi_rpc::RpcCtx, + rpc: Self::Request, + ) -> Result { + Err(xi_rpc::RemoteError::InvalidRequest(None)) + } +} + +impl Plugin { + pub fn initialize(&self) { + self.peer.send_rpc_notification( + "initialize", + &json!({ + "plugin_id": self.id, + }), + ) + } +} + +fn find_all_manifests() -> Vec { + let mut manifest_paths = Vec::new(); + let home = home_dir().unwrap(); + let path = home.join(".lapce").join("plugins"); + path.read_dir().map(|dir| { + dir.flat_map(|item| item.map(|p| p.path()).ok()) + .map(|dir| dir.join("manifest.toml")) + .filter(|f| f.exists()) + .for_each(|f| manifest_paths.push(f)) + }); + eprintln!("proxy mainfiest paths {:?}", manifest_paths); + manifest_paths +} + +fn load_manifest(path: &PathBuf) -> Result { + let mut file = fs::File::open(&path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let mut manifest: PluginDescription = toml::from_str(&contents)?; + // normalize relative paths + //manifest.dir = Some(path.parent().unwrap().canonicalize()?); + // if manifest.exec_path.starts_with("./") { + manifest.dir = path.parent().unwrap().canonicalize()?; + manifest.exec_path = path + .parent() + .unwrap() + .join(manifest.exec_path) + .canonicalize()?; + // } + Ok(manifest) }