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();