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> {
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<NeovimRef> {
@ -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<NeovimRef> {
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()
}

View File

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

View File

@ -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<UiMutex<shell::State>>,
delayed_redraw_event_id: Arc<UiMutex<Option<glib::SourceId>>>,
}
impl 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>) {
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<Value>) -> result::Result<Value, Value> {
fn nvim_cb_req(&self, method: &str, params: Vec<Value>) -> result::Result<Value, Value> {
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<UiMutex<shell::State>>) -> 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<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 {
fn handle_notify(&mut self, name: &str, args: Vec<Value>) {
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::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<UiMutex<shell::State>>,
handler: NvimHandler,
nvim_bin_path: Option<&String>,
timeout: Option<Duration>,
) -> result::Result<Neovim, NvimInitError> {
@ -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)
}

View File

@ -12,9 +12,9 @@ pub enum CursorShape {
impl CursorShape {
fn new(shape_code: &Value) -> Result<CursorShape, String> {
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,

View File

@ -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<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 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<Value>) {
}
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());
}
}

View File

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

View File

@ -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),

View File

@ -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<UiMutex<State>
fn init_nvim_async(
state_arc: Arc<UiMutex<State>>,
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<UiMutex<State>>) {
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));
}
}

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> {
pub fn borrow(&self) -> Ref<T> {
self.assert_ui_thread();