Add general nvim start error message (fix #8)

This commit is contained in:
daa84 2017-07-06 12:41:35 +03:00
parent 5260d78418
commit 62a996f7db
7 changed files with 271 additions and 42 deletions

52
src/error.rs Normal file
View File

@ -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!("<big>Can't start nvim instance:</big>\n\
<i>{}</i>\n\
<span foreground=\"red\"><i>{}</i></span>\n\n\
<big>Possible error reasons:</big>\n\
&#9679; Not supported nvim version (minimum supported version is <b>{}</b>)\n\
&#9679; Error in configuration file (init.vim or ginit.vim)\n\
&#9679; Wrong nvim binary path \
(right path can be passed with <i>--nvim-bin-path=path_here</i> 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) -> &gtk::Box {
&self.base
}
}

View File

@ -31,6 +31,7 @@ mod shell_dlg;
mod popup_menu; mod popup_menu;
mod project; mod project;
mod tabline; mod tabline;
mod error;
use std::env; use std::env;
use gio::ApplicationExt; use gio::ApplicationExt;

View File

@ -1,8 +1,10 @@
use std::io::{Result, Error, ErrorKind}; use std::error;
use std::fmt;
use std::env; use std::env;
use std::process::{Stdio, Command}; use std::process::{Stdio, Command};
use std::result; use std::result;
use std::sync::Arc; use std::sync::Arc;
use std::ops::{Deref, DerefMut};
use neovim_lib::{Handler, Neovim, NeovimApi, Session, Value, UiAttachOptions, CallError, UiOption}; use neovim_lib::{Handler, Neovim, NeovimApi, Session, Value, UiAttachOptions, CallError, UiOption};
use neovim_lib::neovim_api::Tabpage; 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())?) ($exp:expr) => ($exp.as_u64().ok_or("Can't convert argument to u64".to_owned())?)
} }
#[derive(Debug)]
pub struct NvimInitError {
source: Box<error::Error>,
cmd: String,
}
impl NvimInitError {
pub fn new<E>(cmd: &Command, error: E) -> NvimInitError
where E: Into<Box<error::Error>>
{
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<UiMutex<shell::State>>, pub fn initialize(shell: Arc<UiMutex<shell::State>>,
nvim_bin_path: Option<&String>, nvim_bin_path: Option<&String>,
cols: u64, cols: u64,
rows: u64) rows: u64)
-> Result<Neovim> { -> result::Result<Neovim, NvimInitError> {
let mut cmd = if let Some(path) = nvim_bin_path { let mut cmd = if let Some(path) = nvim_bin_path {
Command::new(path) Command::new(path)
} else { } else {
@ -108,10 +151,7 @@ pub fn initialize(shell: Arc<UiMutex<shell::State>>,
let session = Session::new_child_cmd(&mut cmd); let session = Session::new_child_cmd(&mut cmd);
let session = match session { let session = match session {
Err(e) => { Err(e) => return Err(NvimInitError::new(&cmd, e)),
println!("Error execute: {:?}", cmd);
return Err(From::from(e));
}
Ok(s) => s, Ok(s) => s,
}; };
@ -123,9 +163,9 @@ pub fn initialize(shell: Arc<UiMutex<shell::State>>,
opts.set_popupmenu_external(false); opts.set_popupmenu_external(false);
opts.set_tabline_external(true); opts.set_tabline_external(true);
nvim.ui_attach(cols, rows, opts) 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") nvim.command("runtime! ginit.vim")
.map_err(|e| Error::new(ErrorKind::Other, e))?; .map_err(|e| NvimInitError::new(&cmd, e))?;
Ok(nvim) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -9,6 +9,7 @@ use gdk::{EventButton, EventType};
use neovim_lib::{Neovim, NeovimApi}; use neovim_lib::{Neovim, NeovimApi};
use nvim;
use nvim::ErrorReport; use nvim::ErrorReport;
use shell; use shell;
use input; use input;
@ -16,7 +17,7 @@ use input;
const MAX_VISIBLE_ROWS: i32 = 10; const MAX_VISIBLE_ROWS: i32 = 10;
struct State { struct State {
nvim: Option<Rc<RefCell<Neovim>>>, nvim: Option<Rc<RefCell<nvim::NeovimClient>>>,
renderer: gtk::CellRendererText, renderer: gtk::CellRendererText,
tree: gtk::TreeView, tree: gtk::TreeView,
scroll: gtk::ScrolledWindow, scroll: gtk::ScrolledWindow,

View File

@ -20,19 +20,21 @@ use neovim_lib::neovim_api::Tabpage;
use settings::{Settings, FontSource}; use settings::{Settings, FontSource};
use ui_model::{UiModel, Cell, Attrs, Color, ModelRect, COLOR_BLACK, COLOR_WHITE, COLOR_RED}; use ui_model::{UiModel, Cell, Attrs, Color, ModelRect, COLOR_BLACK, COLOR_WHITE, COLOR_RED};
use nvim; use nvim;
use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport}; use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport, NeovimClient};
use input; use input;
use input::keyval_to_input_string; use input::keyval_to_input_string;
use cursor::Cursor; use cursor::Cursor;
use ui::UiMutex; use ui::UiMutex;
use popup_menu::PopupMenu; use popup_menu::PopupMenu;
use tabline::Tabline; 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 { macro_rules! idle_cb_call {
($state:ident.$cb:ident($( $x:expr ),*)) => ( ($state:ident.$cb:ident($( $x:expr ),*)) => (
glib::idle_add(move || { gtk::idle_add(move || {
if let Some(ref cb) = $state.borrow().$cb { if let Some(ref cb) = $state.borrow().$cb {
(&mut *cb.borrow_mut())($($x),*); (&mut *cb.borrow_mut())($($x),*);
} }
@ -58,14 +60,16 @@ pub struct State {
cur_attrs: Option<Attrs>, cur_attrs: Option<Attrs>,
pub mode: NvimMode, pub mode: NvimMode,
mouse_enabled: bool, mouse_enabled: bool,
nvim: Option<Rc<RefCell<Neovim>>>, nvim: Rc<RefCell<NeovimClient>>,
font_desc: FontDescription, font_desc: FontDescription,
cursor: Option<Cursor>, cursor: Option<Cursor>,
popup_menu: RefCell<PopupMenu>, popup_menu: RefCell<PopupMenu>,
settings: Rc<RefCell<Settings>>, settings: Rc<RefCell<Settings>>,
stack: gtk::Stack,
drawing_area: gtk::DrawingArea, drawing_area: gtk::DrawingArea,
tabs: Tabline, tabs: Tabline,
error_area: error::ErrorArea,
line_height: Option<f64>, line_height: Option<f64>,
char_width: Option<f64>, char_width: Option<f64>,
@ -84,7 +88,7 @@ impl State {
State { State {
model: UiModel::new(1, 1), model: UiModel::new(1, 1),
nvim: None, nvim: Rc::new(RefCell::new(NeovimClient::new())),
cur_attrs: None, cur_attrs: None,
bg_color: COLOR_BLACK, bg_color: COLOR_BLACK,
fg_color: COLOR_WHITE, fg_color: COLOR_WHITE,
@ -96,8 +100,10 @@ impl State {
popup_menu, popup_menu,
settings: settings, settings: settings,
stack: gtk::Stack::new(),
drawing_area, drawing_area,
tabs: Tabline::new(), tabs: Tabline::new(),
error_area: error::ErrorArea::new(),
line_height: None, line_height: None,
char_width: None, char_width: None,
@ -119,11 +125,11 @@ impl State {
} }
pub fn nvim(&self) -> RefMut<Neovim> { pub fn nvim(&self) -> RefMut<Neovim> {
self.nvim.as_ref().unwrap().borrow_mut() RefMut::map(self.nvim.borrow_mut(), |n| n.nvim_mut())
} }
pub fn nvim_clone(&self) -> Rc<RefCell<Neovim>> { pub fn nvim_clone(&self) -> Rc<RefCell<NeovimClient>> {
self.nvim.as_ref().unwrap().clone() self.nvim.clone()
} }
pub fn set_detach_cb<F>(&mut self, cb: Option<F>) pub fn set_detach_cb<F>(&mut self, cb: Option<F>)
@ -284,14 +290,27 @@ impl Shell {
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) { pub fn init(&mut self) {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
state.drawing_area.set_hexpand(true); state.drawing_area.set_hexpand(true);
state.drawing_area.set_vexpand(true); state.drawing_area.set_vexpand(true);
state.drawing_area.set_can_focus(true); state.drawing_area.set_can_focus(true);
self.widget.pack_start(&*state.tabs, false, true, 0); let nvim_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
self.widget.pack_start(&state.drawing_area, true, true, 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 state
.drawing_area .drawing_area
@ -516,27 +535,45 @@ fn update_line_metrics(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) {
} }
} }
fn gtk_draw(state: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit { fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit {
update_line_metrics(state, ctx); update_line_metrics(state_arc, ctx);
init_nvim(state); init_nvim(state_arc);
draw(&*state.borrow(), ctx); let mut state = state_arc.borrow_mut();
request_window_resize(&mut *state.borrow_mut()); // in case nvim not initialized
if !state.nvim.borrow().is_error() {
draw(&*state, ctx);
request_window_resize(&mut *state);
}
Inhibit(false) Inhibit(false)
} }
fn init_nvim(state_arc: &Arc<UiMutex<State>>) { fn init_nvim(state_arc: &Arc<UiMutex<State>>) {
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 (cols, rows) = state.calc_nvim_size().unwrap();
let mut nvim = nvim::initialize(state_arc.clone(), let mut nvim = match nvim::initialize(state_arc.clone(),
state.options.nvim_bin_path.as_ref(), state.options.nvim_bin_path.as_ref(),
cols as u64, cols as u64,
rows as u64) rows as u64) {
.expect("Can't start nvim instance"); 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 { if let Some(ref path) = state.options.open_path {
nvim.command(&format!("e {}", path)).report_err(&mut nvim); nvim.command(&format!("e {}", path)).report_err(&mut nvim);
@ -546,12 +583,12 @@ fn init_nvim(state_arc: &Arc<UiMutex<State>>) {
let state_ref = state_arc.clone(); let state_ref = state_arc.clone();
thread::spawn(move || { 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<UiMutex<State>>, _: &EventConfigure) -> bool
if let Some(timer) = state_ref.resize_timer { if let Some(timer) = state_ref.resize_timer {
glib::source_remove(timer); glib::source_remove(timer);
} }
if !state_ref.nvim.borrow().is_initialized() {
return false;
}
if let Some((columns, rows)) = state_ref.calc_nvim_size() { if let Some((columns, rows)) = state_ref.calc_nvim_size() {
let state = state.clone(); let state = state.clone();
state_ref.resize_timer = Some(glib::timeout_add(250, move || { state_ref.resize_timer = Some(glib::timeout_add(250, move || {
@ -967,8 +1009,7 @@ impl RedrawEvents for State {
selected: Tabpage, selected: Tabpage,
tabs: Vec<(Tabpage, Option<&str>)>) tabs: Vec<(Tabpage, Option<&str>)>)
-> RepaintMode { -> RepaintMode {
self.tabs self.tabs.update_tabs(&self.nvim, &selected, &tabs);
.update_tabs(&self.nvim.as_ref().unwrap(), &selected, &tabs);
RepaintMode::Nothing RepaintMode::Nothing
} }

View File

@ -7,15 +7,16 @@ use gtk::prelude::*;
use glib::signal; use glib::signal;
use neovim_lib::{Neovim, NeovimApi}; use neovim_lib::NeovimApi;
use neovim_lib::neovim_api::Tabpage; use neovim_lib::neovim_api::Tabpage;
use nvim;
use nvim::ErrorReport; use nvim::ErrorReport;
struct State { struct State {
data: Vec<Tabpage>, data: Vec<Tabpage>,
selected: Option<Tabpage>, selected: Option<Tabpage>,
nvim: Option<Rc<RefCell<Neovim>>>, nvim: Option<Rc<RefCell<nvim::NeovimClient>>>,
} }
impl State { impl State {
@ -31,7 +32,7 @@ impl State {
let target = &self.data[idx as usize]; let target = &self.data[idx as usize];
if Some(target) != self.selected.as_ref() { if Some(target) != self.selected.as_ref() {
let mut nvim = self.nvim.as_ref().unwrap().borrow_mut(); 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, fn update_state(&self,
nvim: &Rc<RefCell<Neovim>>, nvim: &Rc<RefCell<nvim::NeovimClient>>,
selected: &Tabpage, selected: &Tabpage,
tabs: &Vec<(Tabpage, Option<&str>)>) { tabs: &Vec<(Tabpage, Option<&str>)>) {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
@ -81,7 +82,7 @@ impl Tabline {
} }
pub fn update_tabs(&self, pub fn update_tabs(&self,
nvim: &Rc<RefCell<Neovim>>, nvim: &Rc<RefCell<nvim::NeovimClient>>,
selected: &Tabpage, selected: &Tabpage,
tabs: &Vec<(Tabpage, Option<&str>)>) { tabs: &Vec<(Tabpage, Option<&str>)>) {
if tabs.len() <= 1 { if tabs.len() <= 1 {

View File

@ -159,6 +159,10 @@ fn on_help_about(comps: &Components) {
} }
fn gtk_delete(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> Inhibit { fn gtk_delete(comps: &UiMutex<Components>, shell: &RefCell<Shell>) -> Inhibit {
if !shell.borrow().is_nvim_initialized() {
return Inhibit(false);
}
Inhibit(if shell_dlg::can_close_window(comps, shell) { Inhibit(if shell_dlg::can_close_window(comps, shell) {
let comps = comps.borrow(); let comps = comps.borrow();
comps.close_window(); comps.close_window();