Refactor nvim client

This commit is contained in:
daa84 2017-11-10 18:36:54 +03:00
parent 74514258eb
commit 2ee2fa31be
7 changed files with 202 additions and 125 deletions

View File

@ -1,112 +1,166 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::cell::{Cell, RefCell, RefMut};
use std::sync::{Arc, Mutex, MutexGuard};
use neovim_lib::Neovim; use neovim_lib::Neovim;
#[derive(Clone, Copy, PartialEq)]
enum NeovimClientState { enum NeovimClientState {
Uninitialized, Uninitialized,
InitInProgress, InitInProgress,
Initialized(Neovim), Initialized,
Error, Error,
} }
impl NeovimClientState { pub enum NeovimRef<'a> {
pub fn is_initializing(&self) -> bool { SingleThreaded(RefMut<'a, Neovim>),
MultiThreaded {
guard: MutexGuard<'a, RefCell<Option<Neovim>>>,
nvim: RefMut<'a, Option<Neovim>>,
},
}
impl<'a> NeovimRef<'a> {
fn from_nvim(nvim: RefMut<'a, Neovim>) -> Self {
NeovimRef::SingleThreaded(nvim)
}
fn is_some(&self) -> bool {
match *self { match *self {
NeovimClientState::InitInProgress => true, NeovimRef::MultiThreaded{ref nvim, ..} => nvim.is_some(),
_ => false, NeovimRef::SingleThreaded(_) => true,
} }
} }
pub fn is_uninitialized(&self) -> bool { fn from_nvim_async(nvim_async: &'a NeovimClientAsync) -> Option<NeovimRef<'a>> {
match *self { let guard = nvim_async.nvim.lock().unwrap();
NeovimClientState::Uninitialized => true, let nvim = guard.borrow_mut();
_ => false,
let nvim_ref = NeovimRef::MultiThreaded { guard, nvim };
if nvim_ref.is_some() {
Some(nvim_ref)
} else {
None
} }
} }
}
pub fn is_initialized(&self) -> bool { impl<'a> Deref for NeovimRef<'a> {
type Target = Neovim;
fn deref(&self) -> &Neovim {
match *self { match *self {
NeovimClientState::Initialized(_) => true, NeovimRef::SingleThreaded(ref nvim) => &*nvim,
_ => false, NeovimRef::MultiThreaded { ref nvim, .. } => (&*nvim).as_ref().unwrap(),
} }
} }
}
pub fn nvim(&self) -> &Neovim { impl<'a> DerefMut for NeovimRef<'a> {
fn deref_mut(&mut self) -> &mut Neovim {
match *self { match *self {
NeovimClientState::Initialized(ref nvim) => nvim, NeovimRef::SingleThreaded(ref mut nvim) => &mut *nvim,
NeovimClientState::InitInProgress | NeovimRef::MultiThreaded { ref mut nvim, .. } => (&mut *nvim).as_mut().unwrap(),
NeovimClientState::Uninitialized => panic!("Access to uninitialized neovim client"),
NeovimClientState::Error => {
panic!("Access to neovim client that is not started due to some error")
}
} }
} }
}
pub fn nvim_mut(&mut self) -> &mut Neovim { pub struct NeovimClientAsync {
match *self { nvim: Arc<Mutex<RefCell<Option<Neovim>>>>,
NeovimClientState::Initialized(ref mut nvim) => nvim, }
NeovimClientState::InitInProgress |
NeovimClientState::Uninitialized => panic!("Access to uninitialized neovim client"), impl NeovimClientAsync {
NeovimClientState::Error => { fn new(nvim: Neovim) -> Self {
panic!("Access to neovim client that is not started due to some error") NeovimClientAsync { nvim: Arc::new(Mutex::new(RefCell::new(Some(nvim)))) }
} }
pub fn borrow(&self) -> NeovimRef {
NeovimRef::from_nvim_async(self).unwrap()
}
}
impl Clone for NeovimClientAsync {
fn clone(&self) -> Self {
NeovimClientAsync {
nvim: self.nvim.clone()
} }
} }
} }
pub struct NeovimClient { pub struct NeovimClient {
state: NeovimClientState, state: Cell<NeovimClientState>,
nvim: RefCell<Option<Neovim>>,
nvim_async: RefCell<Option<NeovimClientAsync>>,
} }
impl NeovimClient { impl NeovimClient {
pub fn new() -> Self { pub fn new() -> Self {
NeovimClient { state: NeovimClientState::Uninitialized } NeovimClient {
state: Cell::new(NeovimClientState::Uninitialized),
nvim: RefCell::new(None),
nvim_async: RefCell::new(None),
}
} }
pub fn set_initialized(&mut self, nvim: Neovim) { pub fn async_to_sync(&self) {
self.state = NeovimClientState::Initialized(nvim); {
let lock = self.nvim_async
.borrow()
.as_ref()
.expect("Nvim not initialized")
.nvim
.lock()
.unwrap();
let nvim = lock.borrow_mut().take().unwrap();
*self.nvim.borrow_mut() = Some(nvim);
}
*self.nvim_async.borrow_mut() = None;
} }
pub fn set_error(&mut self) { pub fn set_nvim_async(&self, nvim: Neovim) -> NeovimClientAsync {
self.state = NeovimClientState::Error; let nvim_async = NeovimClientAsync::new(nvim);
*self.nvim_async.borrow_mut() = Some(nvim_async.clone());
nvim_async
} }
pub fn set_in_progress(&mut self) { pub fn set_initialized(&self) {
self.state = NeovimClientState::InitInProgress; self.state.set(NeovimClientState::Initialized);
}
pub fn set_error(&self) {
self.state.set(NeovimClientState::Error);
}
pub fn set_in_progress(&self) {
self.state.set(NeovimClientState::InitInProgress);
} }
pub fn is_initialized(&self) -> bool { pub fn is_initialized(&self) -> bool {
self.state.is_initialized() self.state.get() == NeovimClientState::Initialized
} }
pub fn is_uninitialized(&self) -> bool { pub fn is_uninitialized(&self) -> bool {
self.state.is_uninitialized() self.state.get() == NeovimClientState::Uninitialized
} }
pub fn is_initializing(&self) -> bool { pub fn is_initializing(&self) -> bool {
self.state.is_initializing() self.state.get() == NeovimClientState::InitInProgress
} }
pub fn nvim(&self) -> &Neovim { pub fn nvim(&self) -> Option<NeovimRef> {
self.state.nvim() let nvim = self.nvim.borrow_mut();
} if nvim.is_some() {
Some(NeovimRef::from_nvim(
pub fn nvim_mut(&mut self) -> &mut Neovim { RefMut::map(nvim, |n| n.as_mut().unwrap()),
self.state.nvim_mut() ))
} else {
let nvim_async = self.nvim_async.borrow();
if let Some(ref nvim_async) = *nvim_async {
NeovimRef::from_nvim_async(nvim_async)
} else {
None
}
}
} }
} }
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()
}
}

View File

@ -7,7 +7,7 @@ mod repaint_mode;
pub use self::redraw_handler::{RedrawEvents, GuiApi}; pub use self::redraw_handler::{RedrawEvents, GuiApi};
pub use self::repaint_mode::RepaintMode; pub use self::repaint_mode::RepaintMode;
pub use self::client::NeovimClient; pub use self::client::{NeovimClient, NeovimClientAsync, NeovimRef};
pub use self::mode_info::{ModeInfo, CursorShape}; pub use self::mode_info::{ModeInfo, CursorShape};
use std::error; use std::error;
@ -131,7 +131,7 @@ pub fn start(
} }
pub fn post_start_init( pub fn post_start_init(
nvim: &mut Neovim, nvim: NeovimClientAsync,
open_path: Option<&String>, open_path: Option<&String>,
cols: u64, cols: u64,
rows: u64, rows: u64,
@ -139,15 +139,15 @@ pub fn post_start_init(
let mut opts = UiAttachOptions::new(); let mut opts = UiAttachOptions::new();
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).map_err( nvim.borrow().ui_attach(cols, rows, &opts).map_err(
NvimInitError::new_post_init, NvimInitError::new_post_init,
)?; )?;
nvim.command("runtime! ginit.vim").map_err( nvim.borrow().command("runtime! ginit.vim").map_err(
NvimInitError::new_post_init, NvimInitError::new_post_init,
)?; )?;
if let Some(path) = open_path { if let Some(path) = open_path {
nvim.command(&format!("e {}", path)).map_err( nvim.borrow().command(&format!("e {}", path)).map_err(
NvimInitError::new_post_init, NvimInitError::new_post_init,
)?; )?;
} }

View File

@ -1,5 +1,4 @@
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell;
use super::vim_plug; use super::vim_plug;
use super::store::{Store, PlugInfo}; use super::store::{Store, PlugInfo};
@ -35,7 +34,7 @@ impl Manager {
} }
} }
pub fn init_nvim_client(&mut self, nvim: Rc<RefCell<NeovimClient>>) { pub fn init_nvim_client(&mut self, nvim: Rc<NeovimClient>) {
self.vim_plug.initialize(nvim); self.vim_plug.initialize(nvim);
} }

View File

@ -1,13 +1,12 @@
use std::rc::Rc; use std::rc::Rc;
use std::cell::{RefCell, RefMut};
use neovim_lib::{Neovim, NeovimApi}; use neovim_lib::NeovimApi;
use nvim::{NeovimClient, ErrorReport}; use nvim::{NeovimClient, ErrorReport, NeovimRef};
use value::ValueMapExt; use value::ValueMapExt;
pub struct Manager { pub struct Manager {
nvim: Option<Rc<RefCell<NeovimClient>>>, nvim: Option<Rc<NeovimClient>>,
} }
impl Manager { impl Manager {
@ -15,17 +14,12 @@ impl Manager {
Manager { nvim: None } Manager { nvim: None }
} }
pub fn initialize(&mut self, nvim: Rc<RefCell<NeovimClient>>) { pub fn initialize(&mut self, nvim: Rc<NeovimClient>) {
self.nvim = Some(nvim); self.nvim = Some(nvim);
} }
fn nvim(&self) -> Option<RefMut<Neovim>> { fn nvim(&self) -> Option<NeovimRef> {
let nvim_client = self.nvim.as_ref().unwrap(); self.nvim.as_ref().unwrap().nvim()
if nvim_client.borrow().is_initialized() {
Some(RefMut::map(nvim_client.borrow_mut(), |n| n.nvim_mut()))
} else {
None
}
} }
pub fn get_plugs(&self) -> Result<Box<[VimPlugInfo]>, String> { pub fn get_plugs(&self) -> Result<Box<[VimPlugInfo]>, String> {

View File

@ -17,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<nvim::NeovimClient>>>, nvim: Option<Rc<nvim::NeovimClient>>,
renderer: gtk::CellRendererText, renderer: gtk::CellRendererText,
tree: gtk::TreeView, tree: gtk::TreeView,
scroll: gtk::ScrolledWindow, scroll: gtk::ScrolledWindow,
@ -139,8 +139,11 @@ impl PopupMenu {
let state_ref = state.clone(); let state_ref = state.clone();
state.borrow().tree.connect_button_press_event(move |tree, ev| { state.borrow().tree.connect_button_press_event(move |tree, ev| {
let state = state_ref.borrow(); let state = state_ref.borrow();
let mut nvim = state.nvim.as_ref().unwrap().borrow_mut(); if let Some(mut nvim) = state.nvim.as_ref().unwrap().nvim() {
tree_button_press(tree, ev, &mut *nvim) tree_button_press(tree, ev, &mut *nvim)
} else {
Inhibit(false)
}
}); });
let state_ref = state.clone(); let state_ref = state.clone();
@ -149,8 +152,12 @@ impl PopupMenu {
let state_ref = state.clone(); let state_ref = state.clone();
popover.connect_key_press_event(move |_, ev| { popover.connect_key_press_event(move |_, ev| {
let state = state_ref.borrow(); let state = state_ref.borrow();
let mut nvim = state.nvim.as_ref().unwrap().borrow_mut(); let nvim = state.nvim.as_ref().unwrap();
input::gtk_key_press(&mut *nvim, ev) if let Some(mut nvim) = nvim.nvim() {
input::gtk_key_press(&mut *nvim, ev)
} else {
Inhibit(false)
}
}); });
PopupMenu { PopupMenu {

View File

@ -1,6 +1,6 @@
use std::cell::{RefMut, RefCell, Cell}; use std::cell::{RefCell, Cell};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::{Arc, Condvar, Mutex};
use std::ops::Deref; use std::ops::Deref;
use std::thread; use std::thread;
@ -21,7 +21,7 @@ use settings::{Settings, FontSource};
use ui_model::{UiModel, Attrs, ModelRect}; use ui_model::{UiModel, Attrs, ModelRect};
use color::{ColorModel, Color, COLOR_BLACK, COLOR_WHITE, COLOR_RED}; use color::{ColorModel, Color, COLOR_BLACK, COLOR_WHITE, COLOR_RED};
use nvim; use nvim;
use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport, NeovimClient}; use nvim::{RedrawEvents, GuiApi, RepaintMode, ErrorReport, NeovimClient, NeovimRef, NeovimClientAsync};
use input; use input;
use input::keyval_to_input_string; use input::keyval_to_input_string;
use cursor::Cursor; use cursor::Cursor;
@ -60,7 +60,7 @@ pub struct State {
color_model: ColorModel, color_model: ColorModel,
cur_attrs: Option<Attrs>, cur_attrs: Option<Attrs>,
mouse_enabled: bool, mouse_enabled: bool,
nvim: Rc<RefCell<NeovimClient>>, nvim: Rc<NeovimClient>,
pub font_ctx: render::Context, pub font_ctx: render::Context,
cursor: Option<Cursor>, cursor: Option<Cursor>,
popup_menu: RefCell<PopupMenu>, popup_menu: RefCell<PopupMenu>,
@ -91,7 +91,7 @@ impl State {
State { State {
model: UiModel::empty(), model: UiModel::empty(),
color_model: ColorModel::new(), color_model: ColorModel::new(),
nvim: Rc::new(RefCell::new(NeovimClient::new())), nvim: Rc::new(NeovimClient::new()),
cur_attrs: None, cur_attrs: None,
mouse_enabled: true, mouse_enabled: true,
font_ctx, font_ctx,
@ -125,22 +125,17 @@ impl State {
&self.color_model.bg_color &self.color_model.bg_color
} }
pub fn nvim(&self) -> Option<RefMut<Neovim>> { pub fn nvim(&self) -> Option<NeovimRef> {
if self.nvim.borrow().is_initialized() { self.nvim.nvim()
Some(RefMut::map(self.nvim.borrow_mut(), |n| n.nvim_mut()))
} else {
None
}
} }
pub fn nvim_clone(&self) -> Rc<RefCell<NeovimClient>> { pub fn nvim_clone(&self) -> Rc<NeovimClient> {
self.nvim.clone() self.nvim.clone()
} }
pub fn start_nvim_initialization(&self) -> bool { pub fn start_nvim_initialization(&self) -> bool {
let mut nvim = self.nvim.borrow_mut(); if self.nvim.is_uninitialized() {
if nvim.is_uninitialized() { self.nvim.set_in_progress();
nvim.set_in_progress();
true true
} else { } else {
false false
@ -229,7 +224,9 @@ impl State {
} }
fn im_commit(&self, ch: &str) { fn im_commit(&self, ch: &str) {
input::im_input(&mut self.nvim.borrow_mut(), ch); if let Some(mut nvim) = self.nvim() {
input::im_input(&mut nvim, ch);
}
} }
fn calc_nvim_size(&self) -> (usize, usize) { fn calc_nvim_size(&self) -> (usize, usize) {
@ -270,7 +267,7 @@ impl State {
} }
fn try_nvim_resize(&self) { fn try_nvim_resize(&self) {
if !self.nvim.borrow().is_initialized() { if !self.nvim.is_initialized() {
return; return;
} }
@ -305,9 +302,10 @@ impl State {
gtk::timeout_add(250, move || { gtk::timeout_add(250, move || {
resize_state.set(ResizeState::NvimResizeRequest(columns, rows)); resize_state.set(ResizeState::NvimResizeRequest(columns, rows));
let mut nvim = nvim.borrow_mut(); if let Some(mut nvim) = nvim.nvim() {
if let Err(err) = nvim.ui_try_resize(columns as u64, rows as u64) { if let Err(err) = nvim.ui_try_resize(columns as u64, rows as u64) {
error!("Error trying resize nvim {}", err); error!("Error trying resize nvim {}", err);
}
} }
Continue(false) Continue(false)
}), }),
@ -406,8 +404,7 @@ impl Shell {
pub fn is_nvim_initialized(&self) -> bool { pub fn is_nvim_initialized(&self) -> bool {
let state = self.state.borrow(); let state = self.state.borrow();
let nvim = state.nvim.borrow(); state.nvim.is_initialized()
nvim.is_initialized()
} }
pub fn init(&mut self) { pub fn init(&mut self) {
@ -488,8 +485,8 @@ impl Shell {
{ {
Inhibit(true) Inhibit(true)
} else { } else {
if shell.nvim.borrow().is_initialized() { if let Some(mut nvim) = shell.nvim() {
input::gtk_key_press(&mut shell.nvim.borrow_mut(), ev) input::gtk_key_press(&mut nvim, ev)
} else { } else {
Inhibit(false) Inhibit(false)
} }
@ -716,7 +713,7 @@ fn gtk_motion_notify(shell: &mut State, ui_state: &mut UiState, ev: &EventMotion
fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit { fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit {
let state = state_arc.borrow(); let state = state_arc.borrow();
if state.nvim.borrow().is_initialized() { if state.nvim.is_initialized() {
render::render( render::render(
ctx, ctx,
state.cursor.as_ref().unwrap(), state.cursor.as_ref().unwrap(),
@ -725,7 +722,7 @@ fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit {
&state.color_model, &state.color_model,
&state.mode, &state.mode,
); );
} else if state.nvim.borrow().is_initializing() { } else if state.nvim.is_initializing() {
draw_initializing(&*state, ctx); draw_initializing(&*state, ctx);
} }
@ -738,7 +735,7 @@ fn show_nvim_start_error(err: &nvim::NvimInitError, state_arc: Arc<UiMutex<State
glib::idle_add(move || { glib::idle_add(move || {
let state = state_arc.borrow(); let state = state_arc.borrow();
state.nvim.borrow_mut().set_error(); state.nvim.set_error();
state.error_area.show_nvim_start_error(&source, &cmd); state.error_area.show_nvim_start_error(&source, &cmd);
state.show_error_area(); state.show_error_area();
@ -751,7 +748,7 @@ fn show_nvim_init_error(err: &nvim::NvimInitError, state_arc: Arc<UiMutex<State>
glib::idle_add(move || { glib::idle_add(move || {
let state = state_arc.borrow(); let state = state_arc.borrow();
state.nvim.borrow_mut().set_error(); state.nvim.set_error();
state.error_area.show_nvim_init_error(&source); state.error_area.show_nvim_init_error(&source);
state.show_error_area(); state.show_error_area();
@ -766,7 +763,7 @@ fn init_nvim_async(
rows: usize, rows: usize,
) { ) {
// execute nvim // execute nvim
let mut nvim = match nvim::start(state_arc.clone(), options.nvim_bin_path.as_ref()) { let nvim = match nvim::start(state_arc.clone(), options.nvim_bin_path.as_ref()) {
Ok(nvim) => nvim, Ok(nvim) => nvim,
Err(err) => { Err(err) => {
show_nvim_start_error(&err, state_arc); show_nvim_start_error(&err, state_arc);
@ -774,8 +771,10 @@ fn init_nvim_async(
} }
}; };
let nvim = set_nvim_to_state(state_arc.clone(), nvim);
// add callback on session end // add callback on session end
let guard = nvim.session.take_dispatch_guard(); let guard = nvim.borrow().session.take_dispatch_guard();
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");
@ -785,7 +784,7 @@ fn init_nvim_async(
// attach ui // attach ui
if let Err(err) = nvim::post_start_init( if let Err(err) = nvim::post_start_init(
&mut nvim, nvim,
options.open_path.as_ref(), options.open_path.as_ref(),
cols as u64, cols as u64,
rows as u64, rows as u64,
@ -793,17 +792,40 @@ fn init_nvim_async(
{ {
show_nvim_init_error(&err, state_arc.clone()); show_nvim_init_error(&err, state_arc.clone());
} else { } else {
set_nvim_initialized(nvim, state_arc); set_nvim_initialized(state_arc);
} }
} }
fn set_nvim_initialized(nvim: Neovim, state_arc: Arc<UiMutex<State>>) { fn set_nvim_to_state(state_arc: Arc<UiMutex<State>>, nvim: Neovim) -> NeovimClientAsync {
let mut nvim = Some(nvim); let pair = Arc::new((Mutex::new(None), Condvar::new()));
let pair2 = pair.clone();
glib::idle_add(move || {
let nvim_aync = state_arc.borrow().nvim.set_nvim_async(nvim);
let &(ref lock, ref cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = Some(nvim_aync);
cvar.notify_one();
Continue(false)
});
// Wait idle set nvim properly
let &(ref lock, ref cvar) = &*pair;
let mut started = lock.lock().unwrap();
while started.is_none() {
started = cvar.wait(started).unwrap();
}
started.take().unwrap()
}
fn set_nvim_initialized(state_arc: Arc<UiMutex<State>>) {
glib::idle_add(clone!(state_arc => move || { glib::idle_add(clone!(state_arc => move || {
let mut state = state_arc.borrow_mut(); let mut state = state_arc.borrow_mut();
state.nvim.borrow_mut().set_initialized( state.nvim.async_to_sync();
nvim.take().unwrap(), state.nvim.set_initialized();
);
state.cursor.as_mut().unwrap().start(); state.cursor.as_mut().unwrap().start();
Continue(false) Continue(false)

View File

@ -18,7 +18,7 @@ use nvim::ErrorReport;
struct State { struct State {
data: Vec<Tabpage>, data: Vec<Tabpage>,
selected: Option<Tabpage>, selected: Option<Tabpage>,
nvim: Option<Rc<RefCell<nvim::NeovimClient>>>, nvim: Option<Rc<nvim::NeovimClient>>,
} }
impl State { impl State {
@ -33,8 +33,9 @@ impl State {
fn switch_page(&self, idx: u32) { fn switch_page(&self, idx: u32) {
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(); if let Some(nvim) = self.nvim.as_ref().unwrap().nvim() {
nvim.set_current_tabpage(target).report_err(&mut **nvim); nvim.set_current_tabpage(target).report_err(&mut *nvim);
}
} }
} }
} }
@ -70,7 +71,7 @@ impl Tabline {
fn update_state( fn update_state(
&self, &self,
nvim: &Rc<RefCell<nvim::NeovimClient>>, nvim: &Rc<nvim::NeovimClient>,
selected: &Tabpage, selected: &Tabpage,
tabs: &[(Tabpage, Option<String>)], tabs: &[(Tabpage, Option<String>)],
) { ) {
@ -87,7 +88,7 @@ impl Tabline {
pub fn update_tabs( pub fn update_tabs(
&self, &self,
nvim: &Rc<RefCell<nvim::NeovimClient>>, nvim: &Rc<nvim::NeovimClient>,
selected: &Tabpage, selected: &Tabpage,
tabs: &[(Tabpage, Option<String>)], tabs: &[(Tabpage, Option<String>)],
) { ) {