From e5994b79d476d2869c5ed363453f31ac06f9616f Mon Sep 17 00:00:00 2001 From: daa Date: Sat, 31 Mar 2018 22:57:53 +0300 Subject: [PATCH] Use timeout to hide popup menu this prevent "blinking animation" of menu in case of popup population take some time. --- src/nvim/client.rs | 18 +++-- src/nvim/ext.rs | 1 - src/nvim/handler.rs | 161 +++++++++++++++++++++++++------------ src/nvim/mod.rs | 9 +-- src/nvim/mode_info.rs | 6 +- src/nvim/redraw_handler.rs | 49 +++++------ src/nvim/repaint_mode.rs | 1 - src/popup_menu.rs | 10 +-- src/shell.rs | 8 +- src/ui.rs | 7 ++ 10 files changed, 167 insertions(+), 103 deletions(-) diff --git a/src/nvim/client.rs b/src/nvim/client.rs index a70b98b..5e34554 100644 --- a/src/nvim/client.rs +++ b/src/nvim/client.rs @@ -36,9 +36,7 @@ impl<'a> NeovimRef<'a> { pub fn non_blocked(mut self) -> Option { self.get_mode().ok_and_report().and_then(|mode| { mode.iter() - .find(|kv| { - kv.0.as_str().map(|key| key == "blocking").unwrap_or(false) - }) + .find(|kv| kv.0.as_str().map(|key| key == "blocking").unwrap_or(false)) .map(|kv| kv.1.as_bool().unwrap_or(false)) .and_then(|block| if block { None } else { Some(self) }) }) @@ -71,7 +69,9 @@ pub struct NeovimClientAsync { impl NeovimClientAsync { fn new() -> Self { - NeovimClientAsync { nvim: Arc::new(Mutex::new(None)) } + NeovimClientAsync { + nvim: Arc::new(Mutex::new(None)), + } } pub fn borrow(&self) -> Option { @@ -81,7 +81,9 @@ impl NeovimClientAsync { impl Clone for NeovimClientAsync { 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 { let nvim = self.nvim.borrow_mut(); if nvim.is_some() { - Some(NeovimRef::from_nvim( - RefMut::map(nvim, |n| n.as_mut().unwrap()), - )) + Some(NeovimRef::from_nvim(RefMut::map(nvim, |n| { + n.as_mut().unwrap() + }))) } else { self.nvim_async.borrow() } diff --git a/src/nvim/ext.rs b/src/nvim/ext.rs index 8dc7283..f50961c 100644 --- a/src/nvim/ext.rs +++ b/src/nvim/ext.rs @@ -1,4 +1,3 @@ - use std::result; use neovim_lib::CallError; diff --git a/src/nvim/handler.rs b/src/nvim/handler.rs index 3c62eae..4e471ec 100644 --- a/src/nvim/handler.rs +++ b/src/nvim/handler.rs @@ -1,5 +1,5 @@ use std::result; -use std::sync::{Arc, mpsc}; +use std::sync::{mpsc, Arc}; use neovim_lib::{Handler, Value}; @@ -12,51 +12,60 @@ use super::redraw_handler; pub struct NvimHandler { shell: Arc>, + + delayed_redraw_event_id: Arc>>, } impl NvimHandler { pub fn new(shell: Arc>) -> 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) { match method { "redraw" => { - redraw_handler::remove_uneeded_events(&mut params); + redraw_handler::remove_or_delay_uneeded_events(self, &mut params); - self.safe_call(move |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(()) - }); + self.safe_call(move |ui| call_redraw_handler(params, ui)); } "Gui" => { if !params.is_empty() { @@ -68,7 +77,9 @@ impl NvimHandler { let ui = &mut ui.borrow_mut(); redraw_handler::call_gui_event( 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, )?; ui.on_redraw(&RepaintMode::All); @@ -96,7 +107,7 @@ impl NvimHandler { } } - fn nvim_cb_req (&self, method: &str, params: Vec) -> result::Result { + fn nvim_cb_req(&self, method: &str, params: Vec) -> result::Result { match method { "Gui" => { if !params.is_empty() { @@ -106,11 +117,15 @@ impl NvimHandler { let args = params_iter.collect(); let (sender, receiver) = mpsc::channel(); self.safe_call(move |ui| { - sender.send(redraw_handler::call_gui_request( - &ui.clone(), - req_name.as_str().ok_or_else(|| "Event name does not exists")?, - &args, - )).unwrap(); + sender + .send(redraw_handler::call_gui_request( + &ui.clone(), + req_name + .as_str() + .ok_or_else(|| "Event name does not exists")?, + &args, + )) + .unwrap(); { let ui = &mut ui.borrow_mut(); ui.on_redraw(&RepaintMode::All); @@ -130,7 +145,7 @@ impl NvimHandler { error!("Unsupported request {:?}", params); Err(Value::Nil) } - }, + } _ => { error!("Request {}({:?})", method, params); Err(Value::Nil) @@ -142,17 +157,59 @@ impl NvimHandler { where F: FnOnce(&Arc>) -> result::Result<(), String> + 'static + Send, { - let mut cb = Some(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) - }); + safe_call(self.shell.clone(), cb); } } +fn call_redraw_handler( + params: Vec, + ui: &Arc>, +) -> 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(shell: Arc>, cb: F) +where + F: FnOnce(&Arc>) -> 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 { fn handle_notify(&mut self, name: &str, args: Vec) { self.nvim_cb(name, args); diff --git a/src/nvim/mod.rs b/src/nvim/mod.rs index 95a1c78..5ec8c92 100644 --- a/src/nvim/mod.rs +++ b/src/nvim/mod.rs @@ -10,20 +10,18 @@ pub use self::repaint_mode::RepaintMode; pub use self::client::{NeovimClient, NeovimClientAsync, NeovimRef}; pub use self::mode_info::{CursorShape, ModeInfo}; pub use self::ext::ErrorReport; +pub use self::handler::NvimHandler; use std::error; use std::fmt; use std::env; use std::process::{Command, Stdio}; use std::result; -use std::sync::Arc; use std::time::Duration; use neovim_lib::{Neovim, NeovimApi, NeovimApiAsync, Session, UiAttachOptions}; use misc::escape_filename; -use ui::UiMutex; -use shell; use nvim_config::NvimConfig; #[derive(Debug)] @@ -85,7 +83,7 @@ fn set_windows_creation_flags(cmd: &mut Command) { } pub fn start( - shell: Arc>, + handler: NvimHandler, nvim_bin_path: Option<&String>, timeout: Option, ) -> result::Result { @@ -135,8 +133,7 @@ pub fn start( let mut nvim = Neovim::new(session); - nvim.session - .start_event_loop_handler(handler::NvimHandler::new(shell)); + nvim.session.start_event_loop_handler(handler); Ok(nvim) } diff --git a/src/nvim/mode_info.rs b/src/nvim/mode_info.rs index 4d0de6f..7515d7e 100644 --- a/src/nvim/mode_info.rs +++ b/src/nvim/mode_info.rs @@ -12,9 +12,9 @@ pub enum CursorShape { impl CursorShape { fn new(shape_code: &Value) -> Result { - let str_code = shape_code.as_str().ok_or_else(|| { - "Can't convert cursor shape to string".to_owned() - })?; + let str_code = shape_code + .as_str() + .ok_or_else(|| "Can't convert cursor shape to string".to_owned())?; Ok(match str_code { "block" => CursorShape::Block, diff --git a/src/nvim/redraw_handler.rs b/src/nvim/redraw_handler.rs index f82d55b..865f91b 100644 --- a/src/nvim/redraw_handler.rs +++ b/src/nvim/redraw_handler.rs @@ -13,6 +13,7 @@ use rmpv; use super::repaint_mode::RepaintMode; use super::mode_info::ModeInfo; +use super::handler::NvimHandler; macro_rules! try_str { ($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) } -// 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 // so in case we get both events, just romove one -pub fn remove_uneeded_events(params: &mut Vec) { +// +// 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) { let mut show_popup_finded = false; let mut to_remove = Vec::new(); + let mut delayed_hide_event = None; for (idx, val) in params.iter().enumerate().rev() { if let Some(args) = val.as_array() { match args[0].as_str() { - Some("popupmenu_show") => show_popup_finded = true, - Some("popupmenu_hide") if show_popup_finded => { + Some("popupmenu_show") => { + 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); } _ => (), @@ -267,7 +282,12 @@ pub fn remove_uneeded_events(params: &mut Vec) { } 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() } } - -#[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()); - } -} diff --git a/src/nvim/repaint_mode.rs b/src/nvim/repaint_mode.rs index de967e7..70bc4d6 100644 --- a/src/nvim/repaint_mode.rs +++ b/src/nvim/repaint_mode.rs @@ -38,7 +38,6 @@ impl RepaintMode { } } - #[cfg(test)] mod tests { use super::*; diff --git a/src/popup_menu.rs b/src/popup_menu.rs index 3f5207a..b8c678b 100644 --- a/src/popup_menu.rs +++ b/src/popup_menu.rs @@ -32,6 +32,7 @@ struct State { impl State { pub fn new() -> Self { let tree = gtk::TreeView::new(); + tree.get_selection().set_mode(gtk::SelectionMode::Single); let css_provider = gtk::CssProvider::new(); let style_context = tree.get_style_context().unwrap(); @@ -140,9 +141,6 @@ impl State { self.renderer.set_property_foreground_rgba( Some(&color_model.pmenu_fg().into()), ); - self.renderer.set_property_background_rgba( - Some(&color_model.pmenu_bg().into()), - ); self.update_css(color_model); @@ -164,9 +162,11 @@ impl State { match gtk::CssProviderExt::load_from_data( &self.css_provider, &format!( - ".view {{ color: {}; background-color: {};}}", + ".view :selected {{ color: {}; background-color: {};}}\n + .view {{ background-color: {}; }}", fg.to_hex(), - bg.to_hex() + bg.to_hex(), + color_model.pmenu_bg().to_hex(), ).as_bytes(), ) { Err(e) => error!("Can't update css {}", e), diff --git a/src/shell.rs b/src/shell.rs index fd7a8fa..c1b2ed1 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -24,7 +24,7 @@ use settings::{FontSource, Settings}; use ui_model::{Attrs, ModelRect, UiModel}; use color::{Color, ColorModel, COLOR_BLACK, COLOR_RED, COLOR_WHITE}; use nvim::{self, CompleteItem, ErrorReport, NeovimClient, NeovimClientAsync, NeovimRef, - RepaintMode}; + RepaintMode, NvimHandler}; use input; use input::keyval_to_input_string; use cursor::{BlinkCursor, Cursor, CursorRedrawCb}; @@ -919,13 +919,14 @@ fn show_nvim_init_error(err: &nvim::NvimInitError, state_arc: Arc fn init_nvim_async( state_arc: Arc>, + nvim_handler: NvimHandler, options: ShellOptions, cols: usize, rows: usize, ) { // execute nvim let nvim = match nvim::start( - state_arc.clone(), + nvim_handler, options.nvim_bin_path.as_ref(), options.timeout, ) { @@ -1053,8 +1054,9 @@ fn init_nvim(state_ref: &Arc>) { state.model = UiModel::new(rows as u64, cols as u64); let state_arc = state_ref.clone(); + let nvim_handler = NvimHandler::new(state_ref.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)); } } diff --git a/src/ui.rs b/src/ui.rs index d3831b4..14ef1f0 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -486,6 +486,13 @@ impl UiMutex { } } +impl UiMutex { + pub fn replace(&self, t: T) -> T { + self.assert_ui_thread(); + self.data.replace(t) + } +} + impl UiMutex { pub fn borrow(&self) -> Ref { self.assert_ui_thread();