diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..3ac7992 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,52 @@ +use std::ops::Deref; + +use htmlescape::encode_minimal; + +use gtk; +use gtk::prelude::*; + +use gtk_sys; + +use shell; + +pub struct ErrorArea { + base: gtk::Box, + label: gtk::Label, +} + +impl ErrorArea { + pub fn new() -> Self { + let base = gtk::Box::new(gtk::Orientation::Horizontal, 0); + + let label = gtk::Label::new(None); + label.set_line_wrap(true); + let error_image = gtk::Image::new_from_icon_name("dialog-error", + gtk_sys::GTK_ICON_SIZE_DIALOG as i32); + base.pack_start(&error_image, false, true, 10); + base.pack_start(&label, true, true, 1); + + ErrorArea { base, label } + } + + 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\ + {}\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 option)", + encode_minimal(cmd), encode_minimal(err), shell::MINIMUM_SUPPORTED_NVIM_VERSION)); + self.base.show_all(); + } +} + +impl Deref for ErrorArea { + type Target = gtk::Box; + + fn deref(&self) -> >k::Box { + &self.base + } +} diff --git a/src/main.rs b/src/main.rs index 378a6d0..85fc87f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ mod shell_dlg; mod popup_menu; mod project; mod tabline; +mod error; use std::env; use gio::ApplicationExt; diff --git a/src/nvim.rs b/src/nvim.rs index ada9d8b..f2af5c4 100644 --- a/src/nvim.rs +++ b/src/nvim.rs @@ -1,8 +1,10 @@ -use std::io::{Result, Error, ErrorKind}; +use std::error; +use std::fmt; use std::env; use std::process::{Stdio, Command}; use std::result; use std::sync::Arc; +use std::ops::{Deref, DerefMut}; use neovim_lib::{Handler, Neovim, NeovimApi, Session, Value, UiAttachOptions, CallError, UiOption}; use neovim_lib::neovim_api::Tabpage; @@ -76,11 +78,52 @@ macro_rules! try_uint { ($exp:expr) => ($exp.as_u64().ok_or("Can't convert argument to u64".to_owned())?) } +#[derive(Debug)] +pub struct NvimInitError { + source: Box, + cmd: String, +} + +impl NvimInitError { + pub fn new(cmd: &Command, error: E) -> NvimInitError + where E: Into> + { + NvimInitError { + cmd: format!("{:?}", cmd), + source: error.into(), + } + } + + pub fn source(&self) -> String { + format!("{}", self.source) + } + + pub fn cmd(&self) -> &str { + &self.cmd + } +} + +impl fmt::Display for NvimInitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.source) + } +} + +impl error::Error for NvimInitError { + fn description(&self) -> &str { + "Can't start nvim instance" + } + + fn cause(&self) -> Option<&error::Error> { + Some(&*self.source) + } +} + pub fn initialize(shell: Arc>, nvim_bin_path: Option<&String>, cols: u64, rows: u64) - -> Result { + -> result::Result { let mut cmd = if let Some(path) = nvim_bin_path { Command::new(path) } else { @@ -108,10 +151,7 @@ pub fn initialize(shell: Arc>, let session = Session::new_child_cmd(&mut cmd); let session = match session { - Err(e) => { - println!("Error execute: {:?}", cmd); - return Err(From::from(e)); - } + Err(e) => return Err(NvimInitError::new(&cmd, e)), Ok(s) => s, }; @@ -123,9 +163,9 @@ pub fn initialize(shell: Arc>, opts.set_popupmenu_external(false); opts.set_tabline_external(true); nvim.ui_attach(cols, rows, opts) - .map_err(|e| Error::new(ErrorKind::Other, e))?; + .map_err(|e| NvimInitError::new(&cmd, e))?; nvim.command("runtime! ginit.vim") - .map_err(|e| Error::new(ErrorKind::Other, e))?; + .map_err(|e| NvimInitError::new(&cmd, e))?; Ok(nvim) } @@ -372,6 +412,95 @@ impl RepaintMode { } } + +enum NeovimClientWrapper { + Uninitialized, + Initialized(Neovim), + Error, +} + +impl NeovimClientWrapper { + pub fn is_initialized(&self) -> bool { + match *self { + NeovimClientWrapper::Initialized(_) => true, + _ => false, + } + } + + pub fn is_error(&self) -> bool { + match *self { + NeovimClientWrapper::Error => true, + _ => false, + } + } + + pub fn nvim(&self) -> &Neovim { + match *self { + NeovimClientWrapper::Initialized(ref nvim) => nvim, + NeovimClientWrapper::Uninitialized => panic!("Access to uninitialized neovim client"), + NeovimClientWrapper::Error => panic!("Access to neovim client that is not started due to some error"), + } + } + + 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 => panic!("Access to neovim client that is not started due to some error"), + } + } +} + +pub struct NeovimClient { + nvim: NeovimClientWrapper +} + +impl NeovimClient { + pub fn new() -> Self { + NeovimClient { + nvim: NeovimClientWrapper::Uninitialized, + } + } + + pub fn set_nvim(&mut self, nvim: Neovim) { + self.nvim = NeovimClientWrapper::Initialized(nvim); + } + + pub fn set_error(&mut self) { + self.nvim = NeovimClientWrapper::Error; + } + + pub fn is_initialized(&self) -> bool { + self.nvim.is_initialized() + } + + pub fn is_error(&self) -> bool { + self.nvim.is_error() + } + + pub fn nvim(&self) -> &Neovim { + self.nvim.nvim() + } + + pub fn nvim_mut(&mut self) -> &mut Neovim { + self.nvim.nvim_mut() + } +} + +impl Deref for NeovimClient { + type Target = Neovim; + + fn deref(&self) -> &Neovim { + self.nvim() + } +} + +impl DerefMut for NeovimClient { + fn deref_mut(&mut self) -> &mut Neovim { + self.nvim_mut() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/popup_menu.rs b/src/popup_menu.rs index a8e1ce4..170fd4e 100644 --- a/src/popup_menu.rs +++ b/src/popup_menu.rs @@ -9,6 +9,7 @@ use gdk::{EventButton, EventType}; use neovim_lib::{Neovim, NeovimApi}; +use nvim; use nvim::ErrorReport; use shell; use input; @@ -16,7 +17,7 @@ use input; const MAX_VISIBLE_ROWS: i32 = 10; struct State { - nvim: Option>>, + nvim: Option>>, renderer: gtk::CellRendererText, tree: gtk::TreeView, scroll: gtk::ScrolledWindow, diff --git a/src/shell.rs b/src/shell.rs index 36f38c0..c39caee 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -20,19 +20,21 @@ use neovim_lib::neovim_api::Tabpage; use settings::{Settings, FontSource}; use ui_model::{UiModel, Cell, Attrs, Color, ModelRect, COLOR_BLACK, COLOR_WHITE, COLOR_RED}; use nvim; -use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport}; +use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport, NeovimClient}; use input; use input::keyval_to_input_string; use cursor::Cursor; use ui::UiMutex; use popup_menu::PopupMenu; use tabline::Tabline; +use error; -const DEFAULT_FONT_NAME: &'static str = "DejaVu Sans Mono 12"; +const DEFAULT_FONT_NAME: &str = "DejaVu Sans Mono 12"; +pub const MINIMUM_SUPPORTED_NVIM_VERSION: &str = "0.2"; macro_rules! idle_cb_call { ($state:ident.$cb:ident($( $x:expr ),*)) => ( - glib::idle_add(move || { + gtk::idle_add(move || { if let Some(ref cb) = $state.borrow().$cb { (&mut *cb.borrow_mut())($($x),*); } @@ -58,14 +60,16 @@ pub struct State { cur_attrs: Option, pub mode: NvimMode, mouse_enabled: bool, - nvim: Option>>, + nvim: Rc>, font_desc: FontDescription, cursor: Option, popup_menu: RefCell, settings: Rc>, + stack: gtk::Stack, drawing_area: gtk::DrawingArea, tabs: Tabline, + error_area: error::ErrorArea, line_height: Option, char_width: Option, @@ -84,7 +88,7 @@ impl State { State { model: UiModel::new(1, 1), - nvim: None, + nvim: Rc::new(RefCell::new(NeovimClient::new())), cur_attrs: None, bg_color: COLOR_BLACK, fg_color: COLOR_WHITE, @@ -96,8 +100,10 @@ impl State { popup_menu, settings: settings, + stack: gtk::Stack::new(), drawing_area, tabs: Tabline::new(), + error_area: error::ErrorArea::new(), line_height: None, char_width: None, @@ -119,11 +125,11 @@ impl State { } pub fn nvim(&self) -> RefMut { - self.nvim.as_ref().unwrap().borrow_mut() + RefMut::map(self.nvim.borrow_mut(), |n| n.nvim_mut()) } - pub fn nvim_clone(&self) -> Rc> { - self.nvim.as_ref().unwrap().clone() + pub fn nvim_clone(&self) -> Rc> { + self.nvim.clone() } pub fn set_detach_cb(&mut self, cb: Option) @@ -284,14 +290,27 @@ impl Shell { shell } + pub fn is_nvim_initialized(&self) -> bool { + let state = self.state.borrow(); + let nvim = state.nvim.borrow(); + nvim.is_initialized() + } + pub fn init(&mut self) { let mut state = self.state.borrow_mut(); state.drawing_area.set_hexpand(true); state.drawing_area.set_vexpand(true); state.drawing_area.set_can_focus(true); - self.widget.pack_start(&*state.tabs, false, true, 0); - self.widget.pack_start(&state.drawing_area, true, true, 0); + let nvim_box = gtk::Box::new(gtk::Orientation::Vertical, 0); + + nvim_box.pack_start(&*state.tabs, false, true, 0); + nvim_box.pack_start(&state.drawing_area, true, true, 0); + + state.stack.add_named(&nvim_box, "Nvim"); + state.stack.add_named(&*state.error_area, "Error"); + + self.widget.pack_start(&state.stack, true, true, 0); state .drawing_area @@ -516,27 +535,45 @@ fn update_line_metrics(state_arc: &Arc>, ctx: &cairo::Context) { } } -fn gtk_draw(state: &Arc>, ctx: &cairo::Context) -> Inhibit { - update_line_metrics(state, ctx); - init_nvim(state); +fn gtk_draw(state_arc: &Arc>, ctx: &cairo::Context) -> Inhibit { + update_line_metrics(state_arc, ctx); + init_nvim(state_arc); - draw(&*state.borrow(), ctx); - request_window_resize(&mut *state.borrow_mut()); + let mut state = state_arc.borrow_mut(); + // in case nvim not initialized + if !state.nvim.borrow().is_error() { + draw(&*state, ctx); + request_window_resize(&mut *state); + } Inhibit(false) } fn init_nvim(state_arc: &Arc>) { - let mut state = state_arc.borrow_mut(); + let state = state_arc.borrow(); - if state.nvim.is_none() { + let mut nvim_client = state.nvim.borrow_mut(); + if !nvim_client.is_initialized() && !nvim_client.is_error() { let (cols, rows) = state.calc_nvim_size().unwrap(); - let mut nvim = nvim::initialize(state_arc.clone(), - state.options.nvim_bin_path.as_ref(), - cols as u64, - rows as u64) - .expect("Can't start nvim instance"); + 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); @@ -546,12 +583,12 @@ fn init_nvim(state_arc: &Arc>) { let state_ref = state_arc.clone(); thread::spawn(move || { - guard.join().expect("Can't join dispatch thread"); + guard.join().expect("Can't join dispatch thread"); - idle_cb_call!(state_ref.detach_cb()); - }); + idle_cb_call!(state_ref.detach_cb()); + }); - state.nvim = Some(Rc::new(RefCell::new(nvim))); + nvim_client.set_nvim(nvim); } } @@ -783,6 +820,11 @@ fn gtk_configure_event(state: &Arc>, _: &EventConfigure) -> bool if let Some(timer) = state_ref.resize_timer { glib::source_remove(timer); } + + if !state_ref.nvim.borrow().is_initialized() { + return false; + } + if let Some((columns, rows)) = state_ref.calc_nvim_size() { let state = state.clone(); state_ref.resize_timer = Some(glib::timeout_add(250, move || { @@ -967,8 +1009,7 @@ impl RedrawEvents for State { selected: Tabpage, tabs: Vec<(Tabpage, Option<&str>)>) -> RepaintMode { - self.tabs - .update_tabs(&self.nvim.as_ref().unwrap(), &selected, &tabs); + self.tabs.update_tabs(&self.nvim, &selected, &tabs); RepaintMode::Nothing } diff --git a/src/tabline.rs b/src/tabline.rs index a587a4e..69efe3f 100644 --- a/src/tabline.rs +++ b/src/tabline.rs @@ -7,15 +7,16 @@ use gtk::prelude::*; use glib::signal; -use neovim_lib::{Neovim, NeovimApi}; +use neovim_lib::NeovimApi; use neovim_lib::neovim_api::Tabpage; +use nvim; use nvim::ErrorReport; struct State { data: Vec, selected: Option, - nvim: Option>>, + nvim: Option>>, } impl State { @@ -31,7 +32,7 @@ impl State { let target = &self.data[idx as usize]; if Some(target) != self.selected.as_ref() { let mut nvim = self.nvim.as_ref().unwrap().borrow_mut(); - nvim.set_current_tabpage(&target).report_err(&mut *nvim); + nvim.set_current_tabpage(&target).report_err(&mut **nvim); } } } @@ -66,7 +67,7 @@ impl Tabline { } fn update_state(&self, - nvim: &Rc>, + nvim: &Rc>, selected: &Tabpage, tabs: &Vec<(Tabpage, Option<&str>)>) { let mut state = self.state.borrow_mut(); @@ -81,7 +82,7 @@ impl Tabline { } pub fn update_tabs(&self, - nvim: &Rc>, + nvim: &Rc>, selected: &Tabpage, tabs: &Vec<(Tabpage, Option<&str>)>) { if tabs.len() <= 1 { diff --git a/src/ui.rs b/src/ui.rs index 58d69e3..0548775 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -159,6 +159,10 @@ fn on_help_about(comps: &Components) { } fn gtk_delete(comps: &UiMutex, shell: &RefCell) -> Inhibit { + if !shell.borrow().is_nvim_initialized() { + return Inhibit(false); + } + Inhibit(if shell_dlg::can_close_window(comps, shell) { let comps = comps.borrow(); comps.close_window();