diff --git a/src/error.rs b/src/error.rs index ccda4bb..de58f88 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,19 @@ impl ErrorArea { ErrorArea { base, label } } + pub fn show_nvim_init_error(&self, err: &str) { + error!("Can't initialize nvim: {}", err); + self.label.set_markup(&format!("Can't initialize nvim:\n\ + {}\n\n\ + Possible error reasons:\n\ + ● Not supported nvim version (minimum supported version is {})\n\ + ● Error in configuration file (init.vim or ginit.vim)\n\ + ● Wrong nvim binary path \ + (right path can be passed with --nvim-bin-path=path_here)", + encode_minimal(err), shell::MINIMUM_SUPPORTED_NVIM_VERSION)); + self.base.show_all(); + } + pub fn show_nvim_start_error(&self, err: &str, cmd: &str) { error!("Can't start nvim: {}\nCommand line: {}", err, cmd); self.label.set_markup(&format!("Can't start nvim instance:\n\ diff --git a/src/nvim.rs b/src/nvim.rs index eabcae5..93e5470 100644 --- a/src/nvim.rs +++ b/src/nvim.rs @@ -172,15 +172,24 @@ impl ModeInfo { #[derive(Debug)] pub struct NvimInitError { source: Box, - cmd: String, + cmd: Option, } impl NvimInitError { + pub fn new_post_init(error: E) -> NvimInitError + where E: Into> + { + NvimInitError { + cmd: None, + source: error.into(), + } + } + pub fn new(cmd: &Command, error: E) -> NvimInitError where E: Into> { NvimInitError { - cmd: format!("{:?}", cmd), + cmd: Some(format!("{:?}", cmd)), source: error.into(), } } @@ -189,8 +198,8 @@ impl NvimInitError { format!("{}", self.source) } - pub fn cmd(&self) -> &str { - &self.cmd + pub fn cmd(&self) -> Option<&String> { + self.cmd.as_ref() } } @@ -210,11 +219,9 @@ impl error::Error for NvimInitError { } } -pub fn initialize(shell: Arc>, - nvim_bin_path: Option<&String>, - cols: u64, - rows: u64) - -> result::Result { +pub fn start(shell: Arc>, + nvim_bin_path: Option<&String>) + -> result::Result { let mut cmd = if let Some(path) = nvim_bin_path { Command::new(path) } else { @@ -250,15 +257,29 @@ pub fn initialize(shell: Arc>, nvim.session .start_event_loop_handler(NvimHandler::new(shell)); + + Ok(nvim) +} + +pub fn post_start_init(nvim: &mut Neovim, + open_path: Option<&String>, + cols: u64, + rows: u64) + -> result::Result<(), NvimInitError> { let mut opts = UiAttachOptions::new(); opts.set_popupmenu_external(false); opts.set_tabline_external(true); nvim.ui_attach(cols, rows, opts) - .map_err(|e| NvimInitError::new(&cmd, e))?; + .map_err(|e| NvimInitError::new_post_init(e))?; nvim.command("runtime! ginit.vim") - .map_err(|e| NvimInitError::new(&cmd, e))?; + .map_err(|e| NvimInitError::new_post_init(e))?; - Ok(nvim) + if let Some(path) = open_path { + nvim.command(&format!("e {}", path)) + .map_err(|e| NvimInitError::new_post_init(e))?; + } + + Ok(()) } pub struct NvimHandler { @@ -505,32 +526,41 @@ impl RepaintMode { } -enum NeovimClientWrapper { +enum NeovimClientState { Uninitialized, + InitInProgress, Initialized(Neovim), Error, } -impl NeovimClientWrapper { - pub fn is_initialized(&self) -> bool { +impl NeovimClientState { + pub fn is_initializing(&self) -> bool { match *self { - NeovimClientWrapper::Initialized(_) => true, + NeovimClientState::InitInProgress => true, _ => false, } } - pub fn is_error(&self) -> bool { + pub fn is_uninitialized(&self) -> bool { match *self { - NeovimClientWrapper::Error => true, + NeovimClientState::Uninitialized => true, + _ => false, + } + } + + pub fn is_initialized(&self) -> bool { + match *self { + NeovimClientState::Initialized(_) => true, _ => false, } } pub fn nvim(&self) -> &Neovim { match *self { - NeovimClientWrapper::Initialized(ref nvim) => nvim, - NeovimClientWrapper::Uninitialized => panic!("Access to uninitialized neovim client"), - NeovimClientWrapper::Error => { + NeovimClientState::Initialized(ref nvim) => nvim, + NeovimClientState::InitInProgress | + NeovimClientState::Uninitialized => panic!("Access to uninitialized neovim client"), + NeovimClientState::Error => { panic!("Access to neovim client that is not started due to some error") } } @@ -538,9 +568,10 @@ impl NeovimClientWrapper { pub fn nvim_mut(&mut self) -> &mut Neovim { match *self { - NeovimClientWrapper::Initialized(ref mut nvim) => nvim, - NeovimClientWrapper::Uninitialized => panic!("Access to uninitialized neovim client"), - NeovimClientWrapper::Error => { + NeovimClientState::Initialized(ref mut nvim) => nvim, + NeovimClientState::InitInProgress | + NeovimClientState::Uninitialized => panic!("Access to uninitialized neovim client"), + NeovimClientState::Error => { panic!("Access to neovim client that is not started due to some error") } } @@ -548,36 +579,44 @@ impl NeovimClientWrapper { } pub struct NeovimClient { - nvim: NeovimClientWrapper, + state: NeovimClientState, } impl NeovimClient { pub fn new() -> Self { - NeovimClient { nvim: NeovimClientWrapper::Uninitialized } + NeovimClient { state: NeovimClientState::Uninitialized } } - pub fn set_nvim(&mut self, nvim: Neovim) { - self.nvim = NeovimClientWrapper::Initialized(nvim); + pub fn set_initialized(&mut self, nvim: Neovim) { + self.state = NeovimClientState::Initialized(nvim); } pub fn set_error(&mut self) { - self.nvim = NeovimClientWrapper::Error; + self.state = NeovimClientState::Error; + } + + pub fn set_in_progress(&mut self) { + self.state = NeovimClientState::InitInProgress; } pub fn is_initialized(&self) -> bool { - self.nvim.is_initialized() + self.state.is_initialized() } - pub fn is_error(&self) -> bool { - self.nvim.is_error() + pub fn is_uninitialized(&self) -> bool { + self.state.is_uninitialized() + } + + pub fn is_initializing(&self) -> bool { + self.state.is_initializing() } pub fn nvim(&self) -> &Neovim { - self.nvim.nvim() + self.state.nvim() } pub fn nvim_mut(&mut self) -> &mut Neovim { - self.nvim.nvim_mut() + self.state.nvim_mut() } } diff --git a/src/settings.rs b/src/settings.rs index 1e64cfa..679f9fb 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -7,6 +7,8 @@ use nvim::RepaintMode; use shell::Shell; #[cfg(unix)] use gio; +#[cfg(unix)] +use gio::SettingsExt; #[derive(PartialEq)] pub enum FontSource { diff --git a/src/shell.rs b/src/shell.rs index 837948e..2d55e25 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -9,7 +9,7 @@ use pangocairo::CairoContextExt; use pango; use pango::{LayoutExt, FontDescription}; use gdk; -use gdk::{ModifierType, EventConfigure, EventButton, EventMotion, EventType, EventScroll}; +use gdk::{ModifierType, EventButton, EventMotion, EventType, EventScroll}; use gdk_sys; use glib; use gtk; @@ -70,6 +70,7 @@ pub struct State { line_height: Option, char_width: Option, request_resize: bool, + request_nvim_resize: bool, resize_timer: Option, options: ShellOptions, @@ -108,6 +109,7 @@ impl State { char_width: None, resize_timer: None, request_resize: false, + request_nvim_resize: false, options, @@ -188,6 +190,10 @@ impl State { self.request_resize = true; } + fn request_nvim_resize(&mut self) { + self.request_nvim_resize = true; + } + fn close_popup_menu(&self) { if self.popup_menu.borrow().is_open() { let mut nvim = self.nvim(); @@ -245,6 +251,14 @@ impl State { None } + + fn show_error_area(&self) { + let stack = self.stack.clone(); + gtk::idle_add(move || { + stack.set_visible_child_name("Error"); + Continue(false) + }); + } } pub struct UiState { @@ -257,6 +271,7 @@ impl UiState { } } +#[derive(Clone)] pub struct ShellOptions { nvim_bin_path: Option, open_path: Option, @@ -300,7 +315,7 @@ impl Shell { } pub fn init(&mut self) { - let mut state = self.state.borrow_mut(); + let state = self.state.borrow(); state.drawing_area.set_hexpand(true); state.drawing_area.set_vexpand(true); state.drawing_area.set_can_focus(true); @@ -421,9 +436,10 @@ impl Shell { let ref_state = self.state.clone(); state .drawing_area - .connect_configure_event(move |_, ev| gtk_configure_event(&ref_state, ev)); - - state.cursor.as_mut().unwrap().start(); + .connect_configure_event(move |_, _| { + try_nvim_resize(&ref_state); + false + }); } #[cfg(unix)] @@ -580,60 +596,104 @@ fn update_line_metrics(state_arc: &Arc>, ctx: &cairo::Context) { fn gtk_draw(state_arc: &Arc>, ctx: &cairo::Context) -> Inhibit { update_line_metrics(state_arc, ctx); + + if state_arc.borrow_mut().request_nvim_resize { + try_nvim_resize(state_arc); + } + init_nvim(state_arc); let mut state = state_arc.borrow_mut(); - // in case nvim not initialized - if !state.nvim.borrow().is_error() { + if state.nvim.borrow().is_initialized() { draw(&*state, ctx); request_window_resize(&mut *state); + } else if state.nvim.borrow().is_initializing() { + draw_initializing(&*state, ctx); } Inhibit(false) } +fn show_nvim_start_error(err: nvim::NvimInitError, state_arc: Arc>) { + let source = err.source(); + let cmd = err.cmd().unwrap().to_owned(); + + glib::idle_add(move || { + let state = state_arc.borrow(); + state.nvim.borrow_mut().set_error(); + state.error_area.show_nvim_start_error(&source, &cmd); + state.show_error_area(); + + Continue(false) + }); +} + +fn show_nvim_init_error(err: nvim::NvimInitError, state_arc: Arc>) { + let source = err.source(); + + glib::idle_add(move || { + let state = state_arc.borrow(); + state.nvim.borrow_mut().set_error(); + state.error_area.show_nvim_init_error(&source); + state.show_error_area(); + + Continue(false) + }); +} + +fn init_nvim_async(state_arc: Arc>, + options: ShellOptions, + cols: usize, + rows: usize) { + // execute nvim + let mut nvim = match nvim::start(state_arc.clone(), options.nvim_bin_path.as_ref()) { + Ok(nvim) => nvim, + Err(err) => { + show_nvim_start_error(err, state_arc); + return; + } + }; + + // add callback on session end + let guard = nvim.session.take_dispatch_guard(); + let state_ref = state_arc.clone(); + thread::spawn(move || { + guard.join().expect("Can't join dispatch thread"); + + idle_cb_call!(state_ref.detach_cb()); + }); + + // attach ui + let mut nvim = Some(nvim); + glib::idle_add(move || { + let mut nvim = nvim.take().unwrap(); + if let Err(err) = nvim::post_start_init(&mut nvim, + options.open_path.as_ref(), + cols as u64, + rows as u64) { + show_nvim_init_error(err, state_arc.clone()); + } else { + let mut state = state_arc.borrow_mut(); + state.nvim.borrow_mut().set_initialized(nvim); + state.cursor.as_mut().unwrap().start(); + } + + Continue(false) + }); +} fn init_nvim(state_arc: &Arc>) { let state = state_arc.borrow(); - let mut nvim_client = state.nvim.borrow_mut(); - if !nvim_client.is_initialized() && !nvim_client.is_error() { + let mut nvim = state.nvim.borrow_mut(); + if nvim.is_uninitialized() { + nvim.set_in_progress(); + let (cols, rows) = state.calc_nvim_size().unwrap(); - let mut nvim = match nvim::initialize(state_arc.clone(), - state.options.nvim_bin_path.as_ref(), - cols as u64, - rows as u64) { - Ok(nvim) => nvim, - Err(err) => { - nvim_client.set_error(); - state - .error_area - .show_nvim_start_error(&err.source(), err.cmd()); - let stack = state.stack.clone(); - gtk::idle_add(move || { - stack.set_visible_child_name("Error"); - Continue(false) - }); - - return; - } - }; - - if let Some(ref path) = state.options.open_path { - nvim.command(&format!("e {}", path)).report_err(&mut nvim); - } - - let guard = nvim.session.take_dispatch_guard(); - - let state_ref = state_arc.clone(); - thread::spawn(move || { - guard.join().expect("Can't join dispatch thread"); - - idle_cb_call!(state_ref.detach_cb()); - }); - - nvim_client.set_nvim(nvim); + let state_arc = state_arc.clone(); + let options = state.options.clone(); + thread::spawn(move || init_nvim_async(state_arc, options, cols, rows)); } } @@ -688,6 +748,43 @@ fn draw_backgound(state: &State, } } +fn draw_initializing(state: &State, ctx: &cairo::Context) { + let layout = ctx.create_pango_layout(); + let desc = state.create_pango_font(); + let alloc = state.drawing_area.get_allocation(); + let line_height = state.line_height.unwrap(); + let char_width = state.char_width.unwrap(); + + ctx.set_source_rgb(state.bg_color.0, state.bg_color.1, state.bg_color.2); + ctx.paint(); + + layout.set_font_description(&desc); + layout.set_text("Loading..", -1); + let (width, height) = layout.get_pixel_size(); + + let x = alloc.width as f64 / 2.0 - width as f64 / 2.0; + let y = alloc.height as f64 / 2.0 - height as f64 / 2.0; + + ctx.move_to(x, y); + ctx.set_source_rgb(state.fg_color.0, state.fg_color.1, state.fg_color.2); + ctx.update_pango_layout(&layout); + ctx.show_pango_layout(&layout); + + + ctx.move_to(x + width as f64, y); + state + .cursor + .as_ref() + .unwrap() + .draw(ctx, + state, + char_width, + line_height, + y, + false, + &state.bg_color); +} + fn draw(state: &State, ctx: &cairo::Context) { let layout = ctx.create_pango_layout(); let mut desc = state.create_pango_font(); @@ -754,7 +851,7 @@ fn draw(state: &State, ctx: &cairo::Context) { if !cell.ch.is_whitespace() { update_font_description(&mut desc, &cell.attrs); - layout.set_font_description(Some(&desc)); + layout.set_font_description(&desc); buf.clear(); buf.push(cell.ch); layout.set_text(&buf, -1); @@ -859,15 +956,17 @@ fn split_color(indexed_color: u64) -> Color { Color(r / 255.0, g / 255.0, b / 255.0) } -fn gtk_configure_event(state: &Arc>, _: &EventConfigure) -> bool { +fn try_nvim_resize(state: &Arc>) { let mut state_ref = state.borrow_mut(); + state_ref.request_nvim_resize = false; + if let Some(timer) = state_ref.resize_timer { glib::source_remove(timer); } if !state_ref.nvim.borrow().is_initialized() { - return false; + return; } if let Some((columns, rows)) = state_ref.calc_nvim_size() { @@ -879,13 +978,12 @@ fn gtk_configure_event(state: &Arc>, _: &EventConfigure) -> bool if state_ref.model.rows != rows || state_ref.model.columns != columns { if let Err(err) = state_ref.nvim().ui_try_resize(columns as u64, rows as u64) { - println!("Error trying resize nvim {}", err); + error!("Error trying resize nvim {}", err); } } Continue(false) })); } - false } impl RedrawEvents for State { @@ -1066,7 +1164,7 @@ impl RedrawEvents for State { impl GuiApi for State { fn set_font(&mut self, font_desc: &str) { self.set_font_desc(font_desc); - self.request_resize(); + self.request_nvim_resize(); let mut settings = self.settings.borrow_mut(); settings.set_font_source(FontSource::Rpc);