Use timeout to hide popup menu

this prevent "blinking animation" of menu in case of popup population take
some time.
This commit is contained in:
daa 2018-03-31 22:57:53 +03:00
parent 82c7f0818a
commit e5994b79d4
10 changed files with 167 additions and 103 deletions

View File

@ -36,9 +36,7 @@ impl<'a> NeovimRef<'a> {
pub fn non_blocked(mut self) -> Option<Self> { pub fn non_blocked(mut self) -> Option<Self> {
self.get_mode().ok_and_report().and_then(|mode| { self.get_mode().ok_and_report().and_then(|mode| {
mode.iter() mode.iter()
.find(|kv| { .find(|kv| kv.0.as_str().map(|key| key == "blocking").unwrap_or(false))
kv.0.as_str().map(|key| key == "blocking").unwrap_or(false)
})
.map(|kv| kv.1.as_bool().unwrap_or(false)) .map(|kv| kv.1.as_bool().unwrap_or(false))
.and_then(|block| if block { None } else { Some(self) }) .and_then(|block| if block { None } else { Some(self) })
}) })
@ -71,7 +69,9 @@ pub struct NeovimClientAsync {
impl NeovimClientAsync { impl NeovimClientAsync {
fn new() -> Self { fn new() -> Self {
NeovimClientAsync { nvim: Arc::new(Mutex::new(None)) } NeovimClientAsync {
nvim: Arc::new(Mutex::new(None)),
}
} }
pub fn borrow(&self) -> Option<NeovimRef> { pub fn borrow(&self) -> Option<NeovimRef> {
@ -81,7 +81,9 @@ impl NeovimClientAsync {
impl Clone for NeovimClientAsync { impl Clone for NeovimClientAsync {
fn clone(&self) -> Self { fn clone(&self) -> Self {
NeovimClientAsync { nvim: self.nvim.clone() } NeovimClientAsync {
nvim: self.nvim.clone(),
}
} }
} }
@ -147,9 +149,9 @@ impl NeovimClient {
pub fn nvim(&self) -> Option<NeovimRef> { pub fn nvim(&self) -> Option<NeovimRef> {
let nvim = self.nvim.borrow_mut(); let nvim = self.nvim.borrow_mut();
if nvim.is_some() { if nvim.is_some() {
Some(NeovimRef::from_nvim( Some(NeovimRef::from_nvim(RefMut::map(nvim, |n| {
RefMut::map(nvim, |n| n.as_mut().unwrap()), n.as_mut().unwrap()
)) })))
} else { } else {
self.nvim_async.borrow() self.nvim_async.borrow()
} }

View File

@ -1,4 +1,3 @@
use std::result; use std::result;
use neovim_lib::CallError; use neovim_lib::CallError;

View File

@ -1,5 +1,5 @@
use std::result; use std::result;
use std::sync::{Arc, mpsc}; use std::sync::{mpsc, Arc};
use neovim_lib::{Handler, Value}; use neovim_lib::{Handler, Value};
@ -12,51 +12,60 @@ use super::redraw_handler;
pub struct NvimHandler { pub struct NvimHandler {
shell: Arc<UiMutex<shell::State>>, shell: Arc<UiMutex<shell::State>>,
delayed_redraw_event_id: Arc<UiMutex<Option<glib::SourceId>>>,
} }
impl NvimHandler { impl NvimHandler {
pub fn new(shell: Arc<UiMutex<shell::State>>) -> NvimHandler { pub fn new(shell: Arc<UiMutex<shell::State>>) -> NvimHandler {
NvimHandler { shell: shell } NvimHandler {
shell,
delayed_redraw_event_id: Arc::new(UiMutex::new(None)),
}
}
pub fn schedule_redraw_event(&self, event: Value) {
let shell = self.shell.clone();
let delayed_redraw_event_id = self.delayed_redraw_event_id.clone();
glib::idle_add(move || {
let id = Some(glib::timeout_add(
250,
clone!(shell, event, delayed_redraw_event_id => move || {
delayed_redraw_event_id.replace(None);
if let Err(msg) = call_redraw_handler(vec![event.clone()], &shell) {
error!("Error call function: {}", msg);
}
glib::Continue(false)
}),
));
delayed_redraw_event_id.replace(id);
glib::Continue(false)
});
}
pub fn remove_scheduled_redraw_event(&self) {
let delayed_redraw_event_id = self.delayed_redraw_event_id.clone();
glib::idle_add(move || {
let id = delayed_redraw_event_id.replace(None);
if let Some(ev_id) = id {
glib::source_remove(ev_id);
}
glib::Continue(false)
});
} }
fn nvim_cb(&self, method: &str, mut params: Vec<Value>) { fn nvim_cb(&self, method: &str, mut params: Vec<Value>) {
match method { match method {
"redraw" => { "redraw" => {
redraw_handler::remove_uneeded_events(&mut params); redraw_handler::remove_or_delay_uneeded_events(self, &mut params);
self.safe_call(move |ui| { self.safe_call(move |ui| call_redraw_handler(params, ui));
let ui = &mut ui.borrow_mut();
let mut repaint_mode = RepaintMode::Nothing;
for ev in params {
if let Value::Array(ev_args) = ev {
let mut args_iter = ev_args.into_iter();
let ev_name = args_iter.next();
if let Some(ev_name) = ev_name {
if let Some(ev_name) = ev_name.as_str() {
for local_args in args_iter {
let args = match local_args {
Value::Array(ar) => ar,
_ => vec![],
};
let call_reapint_mode =
redraw_handler::call(ui, &ev_name, args)?;
repaint_mode = repaint_mode.join(call_reapint_mode);
}
} else {
error!("Unsupported event");
}
} else {
error!("Event name does not exists");
}
} else {
error!("Unsupported event type {:?}", ev);
}
}
ui.on_redraw(&repaint_mode);
Ok(())
});
} }
"Gui" => { "Gui" => {
if !params.is_empty() { if !params.is_empty() {
@ -68,7 +77,9 @@ impl NvimHandler {
let ui = &mut ui.borrow_mut(); let ui = &mut ui.borrow_mut();
redraw_handler::call_gui_event( redraw_handler::call_gui_event(
ui, ui,
ev_name.as_str().ok_or_else(|| "Event name does not exists")?, ev_name
.as_str()
.ok_or_else(|| "Event name does not exists")?,
args, args,
)?; )?;
ui.on_redraw(&RepaintMode::All); ui.on_redraw(&RepaintMode::All);
@ -96,7 +107,7 @@ impl NvimHandler {
} }
} }
fn nvim_cb_req (&self, method: &str, params: Vec<Value>) -> result::Result<Value, Value> { fn nvim_cb_req(&self, method: &str, params: Vec<Value>) -> result::Result<Value, Value> {
match method { match method {
"Gui" => { "Gui" => {
if !params.is_empty() { if !params.is_empty() {
@ -106,11 +117,15 @@ impl NvimHandler {
let args = params_iter.collect(); let args = params_iter.collect();
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
self.safe_call(move |ui| { self.safe_call(move |ui| {
sender.send(redraw_handler::call_gui_request( sender
&ui.clone(), .send(redraw_handler::call_gui_request(
req_name.as_str().ok_or_else(|| "Event name does not exists")?, &ui.clone(),
&args, req_name
)).unwrap(); .as_str()
.ok_or_else(|| "Event name does not exists")?,
&args,
))
.unwrap();
{ {
let ui = &mut ui.borrow_mut(); let ui = &mut ui.borrow_mut();
ui.on_redraw(&RepaintMode::All); ui.on_redraw(&RepaintMode::All);
@ -130,7 +145,7 @@ impl NvimHandler {
error!("Unsupported request {:?}", params); error!("Unsupported request {:?}", params);
Err(Value::Nil) Err(Value::Nil)
} }
}, }
_ => { _ => {
error!("Request {}({:?})", method, params); error!("Request {}({:?})", method, params);
Err(Value::Nil) Err(Value::Nil)
@ -142,17 +157,59 @@ impl NvimHandler {
where where
F: FnOnce(&Arc<UiMutex<shell::State>>) -> result::Result<(), String> + 'static + Send, F: FnOnce(&Arc<UiMutex<shell::State>>) -> result::Result<(), String> + 'static + Send,
{ {
let mut cb = Some(cb); safe_call(self.shell.clone(), cb);
let shell = self.shell.clone();
glib::idle_add(move || {
if let Err(msg) = cb.take().unwrap()(&shell) {
error!("Error call function: {}", msg);
}
glib::Continue(false)
});
} }
} }
fn call_redraw_handler(
params: Vec<Value>,
ui: &Arc<UiMutex<shell::State>>,
) -> result::Result<(), String> {
let ui = &mut ui.borrow_mut();
let mut repaint_mode = RepaintMode::Nothing;
for ev in params {
if let Value::Array(ev_args) = ev {
let mut args_iter = ev_args.into_iter();
let ev_name = args_iter.next();
if let Some(ev_name) = ev_name {
if let Some(ev_name) = ev_name.as_str() {
for local_args in args_iter {
let args = match local_args {
Value::Array(ar) => ar,
_ => vec![],
};
let call_reapint_mode = redraw_handler::call(ui, &ev_name, args)?;
repaint_mode = repaint_mode.join(call_reapint_mode);
}
} else {
error!("Unsupported event");
}
} else {
error!("Event name does not exists");
}
} else {
error!("Unsupported event type {:?}", ev);
}
}
ui.on_redraw(&repaint_mode);
Ok(())
}
fn safe_call<F>(shell: Arc<UiMutex<shell::State>>, cb: F)
where
F: FnOnce(&Arc<UiMutex<shell::State>>) -> result::Result<(), String> + 'static + Send,
{
let mut cb = Some(cb);
glib::idle_add(move || {
if let Err(msg) = cb.take().unwrap()(&shell) {
error!("Error call function: {}", msg);
}
glib::Continue(false)
});
}
impl Handler for NvimHandler { impl Handler for NvimHandler {
fn handle_notify(&mut self, name: &str, args: Vec<Value>) { fn handle_notify(&mut self, name: &str, args: Vec<Value>) {
self.nvim_cb(name, args); self.nvim_cb(name, args);

View File

@ -10,20 +10,18 @@ pub use self::repaint_mode::RepaintMode;
pub use self::client::{NeovimClient, NeovimClientAsync, NeovimRef}; pub use self::client::{NeovimClient, NeovimClientAsync, NeovimRef};
pub use self::mode_info::{CursorShape, ModeInfo}; pub use self::mode_info::{CursorShape, ModeInfo};
pub use self::ext::ErrorReport; pub use self::ext::ErrorReport;
pub use self::handler::NvimHandler;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::env; use std::env;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::result; use std::result;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use neovim_lib::{Neovim, NeovimApi, NeovimApiAsync, Session, UiAttachOptions}; use neovim_lib::{Neovim, NeovimApi, NeovimApiAsync, Session, UiAttachOptions};
use misc::escape_filename; use misc::escape_filename;
use ui::UiMutex;
use shell;
use nvim_config::NvimConfig; use nvim_config::NvimConfig;
#[derive(Debug)] #[derive(Debug)]
@ -85,7 +83,7 @@ fn set_windows_creation_flags(cmd: &mut Command) {
} }
pub fn start( pub fn start(
shell: Arc<UiMutex<shell::State>>, handler: NvimHandler,
nvim_bin_path: Option<&String>, nvim_bin_path: Option<&String>,
timeout: Option<Duration>, timeout: Option<Duration>,
) -> result::Result<Neovim, NvimInitError> { ) -> result::Result<Neovim, NvimInitError> {
@ -135,8 +133,7 @@ pub fn start(
let mut nvim = Neovim::new(session); let mut nvim = Neovim::new(session);
nvim.session nvim.session.start_event_loop_handler(handler);
.start_event_loop_handler(handler::NvimHandler::new(shell));
Ok(nvim) Ok(nvim)
} }

View File

@ -12,9 +12,9 @@ pub enum CursorShape {
impl CursorShape { impl CursorShape {
fn new(shape_code: &Value) -> Result<CursorShape, String> { fn new(shape_code: &Value) -> Result<CursorShape, String> {
let str_code = shape_code.as_str().ok_or_else(|| { let str_code = shape_code
"Can't convert cursor shape to string".to_owned() .as_str()
})?; .ok_or_else(|| "Can't convert cursor shape to string".to_owned())?;
Ok(match str_code { Ok(match str_code {
"block" => CursorShape::Block, "block" => CursorShape::Block,

View File

@ -13,6 +13,7 @@ use rmpv;
use super::repaint_mode::RepaintMode; use super::repaint_mode::RepaintMode;
use super::mode_info::ModeInfo; use super::mode_info::ModeInfo;
use super::handler::NvimHandler;
macro_rules! try_str { macro_rules! try_str {
($exp:expr) => ($exp.as_str().ok_or_else(|| "Can't convert argument to string".to_owned())?) ($exp:expr) => ($exp.as_str().ok_or_else(|| "Can't convert argument to string".to_owned())?)
@ -247,18 +248,32 @@ pub fn call(
Ok(repaint_mode) Ok(repaint_mode)
} }
// menu content update call popupmenu_hide followed by popupmenu_show // Here two cases processed:
//
// 1. menu content update call popupmenu_hide followed by popupmenu_show in same batch
// this generates unneded hide event // this generates unneded hide event
// so in case we get both events, just romove one // so in case we get both events, just romove one
pub fn remove_uneeded_events(params: &mut Vec<Value>) { //
// 2. hide event postpone in case show event come bit later
// but in new event batch
pub fn remove_or_delay_uneeded_events(handler: &NvimHandler, params: &mut Vec<Value>) {
let mut show_popup_finded = false; let mut show_popup_finded = false;
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
let mut delayed_hide_event = None;
for (idx, val) in params.iter().enumerate().rev() { for (idx, val) in params.iter().enumerate().rev() {
if let Some(args) = val.as_array() { if let Some(args) = val.as_array() {
match args[0].as_str() { match args[0].as_str() {
Some("popupmenu_show") => show_popup_finded = true, Some("popupmenu_show") => {
Some("popupmenu_hide") if show_popup_finded => { show_popup_finded = true;
handler.remove_scheduled_redraw_event();
}
Some("popupmenu_hide") if !show_popup_finded && delayed_hide_event.is_none() => {
to_remove.push(idx);
delayed_hide_event = Some(idx);
handler.remove_scheduled_redraw_event();
}
Some("popupmenu_hide") => {
to_remove.push(idx); to_remove.push(idx);
} }
_ => (), _ => (),
@ -267,7 +282,12 @@ pub fn remove_uneeded_events(params: &mut Vec<Value>) {
} }
to_remove.iter().for_each(|&idx| { to_remove.iter().for_each(|&idx| {
params.remove(idx); let ev = params.remove(idx);
if let Some(delayed_hide_event_idx) = delayed_hide_event {
if delayed_hide_event_idx == idx {
handler.schedule_redraw_event(ev);
}
}
}); });
} }
@ -290,22 +310,3 @@ impl<'a> CompleteItem<'a> {
.collect() .collect()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_popup_menu_hide() {
// remove only first hide
let mut params = vec![
Value::from(vec![Value::from("popupmenu_hide")]),
Value::from(vec![Value::from("popupmenu_show")]),
Value::from(vec![Value::from("popupmenu_hide")]),
];
remove_uneeded_events(&mut params);
assert_eq!(2, params.len());
}
}

View File

@ -38,7 +38,6 @@ impl RepaintMode {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -32,6 +32,7 @@ struct State {
impl State { impl State {
pub fn new() -> Self { pub fn new() -> Self {
let tree = gtk::TreeView::new(); let tree = gtk::TreeView::new();
tree.get_selection().set_mode(gtk::SelectionMode::Single);
let css_provider = gtk::CssProvider::new(); let css_provider = gtk::CssProvider::new();
let style_context = tree.get_style_context().unwrap(); let style_context = tree.get_style_context().unwrap();
@ -140,9 +141,6 @@ impl State {
self.renderer.set_property_foreground_rgba( self.renderer.set_property_foreground_rgba(
Some(&color_model.pmenu_fg().into()), Some(&color_model.pmenu_fg().into()),
); );
self.renderer.set_property_background_rgba(
Some(&color_model.pmenu_bg().into()),
);
self.update_css(color_model); self.update_css(color_model);
@ -164,9 +162,11 @@ impl State {
match gtk::CssProviderExt::load_from_data( match gtk::CssProviderExt::load_from_data(
&self.css_provider, &self.css_provider,
&format!( &format!(
".view {{ color: {}; background-color: {};}}", ".view :selected {{ color: {}; background-color: {};}}\n
.view {{ background-color: {}; }}",
fg.to_hex(), fg.to_hex(),
bg.to_hex() bg.to_hex(),
color_model.pmenu_bg().to_hex(),
).as_bytes(), ).as_bytes(),
) { ) {
Err(e) => error!("Can't update css {}", e), Err(e) => error!("Can't update css {}", e),

View File

@ -24,7 +24,7 @@ use settings::{FontSource, Settings};
use ui_model::{Attrs, ModelRect, UiModel}; use ui_model::{Attrs, ModelRect, UiModel};
use color::{Color, ColorModel, COLOR_BLACK, COLOR_RED, COLOR_WHITE}; use color::{Color, ColorModel, COLOR_BLACK, COLOR_RED, COLOR_WHITE};
use nvim::{self, CompleteItem, ErrorReport, NeovimClient, NeovimClientAsync, NeovimRef, use nvim::{self, CompleteItem, ErrorReport, NeovimClient, NeovimClientAsync, NeovimRef,
RepaintMode}; RepaintMode, NvimHandler};
use input; use input;
use input::keyval_to_input_string; use input::keyval_to_input_string;
use cursor::{BlinkCursor, Cursor, CursorRedrawCb}; use cursor::{BlinkCursor, Cursor, CursorRedrawCb};
@ -919,13 +919,14 @@ fn show_nvim_init_error(err: &nvim::NvimInitError, state_arc: Arc<UiMutex<State>
fn init_nvim_async( fn init_nvim_async(
state_arc: Arc<UiMutex<State>>, state_arc: Arc<UiMutex<State>>,
nvim_handler: NvimHandler,
options: ShellOptions, options: ShellOptions,
cols: usize, cols: usize,
rows: usize, rows: usize,
) { ) {
// execute nvim // execute nvim
let nvim = match nvim::start( let nvim = match nvim::start(
state_arc.clone(), nvim_handler,
options.nvim_bin_path.as_ref(), options.nvim_bin_path.as_ref(),
options.timeout, options.timeout,
) { ) {
@ -1053,8 +1054,9 @@ fn init_nvim(state_ref: &Arc<UiMutex<State>>) {
state.model = UiModel::new(rows as u64, cols as u64); state.model = UiModel::new(rows as u64, cols as u64);
let state_arc = state_ref.clone(); let state_arc = state_ref.clone();
let nvim_handler = NvimHandler::new(state_ref.clone());
let options = state.options.clone(); let options = state.options.clone();
thread::spawn(move || init_nvim_async(state_arc, options, cols, rows)); thread::spawn(move || init_nvim_async(state_arc, nvim_handler, options, cols, rows));
} }
} }

View File

@ -486,6 +486,13 @@ impl<T> UiMutex<T> {
} }
} }
impl <T> UiMutex<T> {
pub fn replace(&self, t: T) -> T {
self.assert_ui_thread();
self.data.replace(t)
}
}
impl<T: ?Sized> UiMutex<T> { impl<T: ?Sized> UiMutex<T> {
pub fn borrow(&self) -> Ref<T> { pub fn borrow(&self) -> Ref<T> {
self.assert_ui_thread(); self.assert_ui_thread();