diff --git a/src/cmd_line.rs b/src/cmd_line.rs index dcdf9f2..03dbfc3 100644 --- a/src/cmd_line.rs +++ b/src/cmd_line.rs @@ -2,20 +2,23 @@ use std::collections::HashMap; use std::rc::Rc; use std::sync::Arc; use std::cell::RefCell; -use std::cmp::max; +use std::cmp::{max, min}; use gtk; use gtk::prelude::*; use cairo; +use pango; use neovim_lib::Value; +use nvim::{self, NeovimClient}; use mode; use ui_model::{Attrs, ModelLayout}; use ui::UiMutex; use render::{self, CellMetrics}; use shell; use cursor; +use popup_menu; pub struct Level { model_layout: ModelLayout, @@ -171,6 +174,7 @@ fn prompt_lines( } struct State { + nvim: Option>, levels: Vec, block: Option, render_state: Rc>, @@ -181,6 +185,7 @@ struct State { impl State { fn new(drawing_area: gtk::DrawingArea, render_state: Rc>) -> Self { State { + nvim: None, levels: Vec::new(), block: None, render_state, @@ -262,6 +267,11 @@ impl cursor::CursorRedrawCb for State { pub struct CmdLine { popover: gtk::Popover, + wild_tree: gtk::TreeView, + wild_scroll: gtk::ScrolledWindow, + wild_css_provider: gtk::CssProvider, + wild_renderer: gtk::CellRendererText, + wild_column: gtk::TreeViewColumn, displyed: bool, state: Arc>, } @@ -272,9 +282,10 @@ impl CmdLine { popover.set_modal(false); popover.set_position(gtk::PositionType::Right); + let content = gtk::Box::new(gtk::Orientation::Vertical, 0); + let drawing_area = gtk::DrawingArea::new(); - drawing_area.show_all(); - popover.add(&drawing_area); + content.pack_start(&drawing_area, true, true, 0); let state = Arc::new(UiMutex::new(State::new(drawing_area.clone(), render_state))); let weak_cb = Arc::downgrade(&state); @@ -283,15 +294,76 @@ impl CmdLine { drawing_area.connect_draw(clone!(state => move |_, ctx| gtk_draw(ctx, &state))); + let (wild_scroll, wild_tree, wild_css_provider, wild_renderer, wild_column) = + CmdLine::create_widlmenu(&state); + content.pack_start(&wild_scroll, false, true, 0); + popover.add(&content); + + drawing_area.show_all(); + content.show(); + CmdLine { popover, state, displyed: false, + wild_scroll, + wild_tree, + wild_css_provider, + wild_renderer, + wild_column, } } + fn create_widlmenu( + state: &Arc>, + ) -> ( + gtk::ScrolledWindow, + gtk::TreeView, + gtk::CssProvider, + gtk::CellRendererText, + gtk::TreeViewColumn, + ) { + let css_provider = gtk::CssProvider::new(); + + let tree = gtk::TreeView::new(); + let style_context = tree.get_style_context().unwrap(); + style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + + tree.get_selection().set_mode(gtk::SelectionMode::Single); + tree.set_headers_visible(false); + tree.set_can_focus(false); + + let renderer = gtk::CellRendererText::new(); + renderer.set_property_ellipsize(pango::EllipsizeMode::End); + + let column = gtk::TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.add_attribute(&renderer, "text", 0); + tree.append_column(&column); + + let scroll = gtk::ScrolledWindow::new(None, None); + scroll.set_propagate_natural_height(true); + scroll.set_propagate_natural_width(true); + + scroll.add(&tree); + + tree.connect_button_press_event(clone!(state => move |tree, ev| { + let state = state.borrow(); + let nvim = state.nvim.as_ref().unwrap().nvim(); + if let Some(mut nvim) = nvim { + popup_menu::tree_button_press(tree, ev, &mut *nvim, ""); + } + Inhibit(false) + })); + + (scroll, tree, css_provider, renderer, column) + } + pub fn show_level(&mut self, ctx: &CmdLineContext) { let mut state = self.state.borrow_mut(); + if state.nvim.is_none() { + state.nvim = Some(ctx.nvim.clone()); + } let render_state = state.render_state.clone(); let render_state = render_state.borrow(); @@ -405,6 +477,65 @@ impl CmdLine { .unwrap() .set_mode_info(mode_info); } + + pub fn show_wildmenu( + &self, + items: Vec, + render_state: &shell::RenderState, + max_width: i32, + ) { + // update font/color + self.wild_renderer + .set_property_font(Some(&render_state.font_ctx.font_description().to_string())); + + self.wild_renderer + .set_property_foreground_rgba(Some(&render_state.color_model.pmenu_fg().into())); + + popup_menu::update_css(&self.wild_css_provider, &render_state.color_model); + + // set width + // this calculation produce width more then needed, but this is looks ok :) + let max_item_width = (items.iter().map(|item| item.len()).max().unwrap() as f64 + * render_state.font_ctx.cell_metrics().char_width) as i32 + + self.state.borrow().levels.last().unwrap().preferred_width; + self.wild_column + .set_fixed_width(min(max_item_width, max_width)); + self.wild_scroll.set_max_content_width(max_width); + + // load data + let list_store = gtk::ListStore::new(&vec![gtk::Type::String; 1]); + for item in items { + list_store.insert_with_values(None, &[0], &[&item]); + } + self.wild_tree.set_model(&list_store); + + // set height + let treeview_height = + popup_menu::calc_treeview_height(&self.wild_tree, &self.wild_renderer); + + self.wild_scroll.set_max_content_height(treeview_height); + + self.wild_scroll.show_all(); + } + + pub fn hide_wildmenu(&self) { + self.wild_scroll.hide(); + } + + pub fn wildmenu_select(&self, selected: i64) { + if selected >= 0 { + let wild_tree = self.wild_tree.clone(); + idle_add(move || { + let selected_path = gtk::TreePath::new_from_string(&format!("{}", selected)); + wild_tree.get_selection().select_path(&selected_path); + wild_tree.scroll_to_cell(&selected_path, None, false, 0.0, 0.0); + + Continue(false) + }); + } else { + self.wild_tree.get_selection().unselect_all(); + } + } } fn gtk_draw(ctx: &cairo::Context, state: &Arc>) -> Inhibit { @@ -446,7 +577,8 @@ fn gtk_draw(ctx: &cairo::Context, state: &Arc>) -> Inhibit { Inhibit(false) } -pub struct CmdLineContext { +pub struct CmdLineContext<'a> { + pub nvim: &'a Rc, pub content: Vec<(HashMap, String)>, pub pos: u64, pub firstc: String, @@ -460,7 +592,7 @@ pub struct CmdLineContext { pub max_width: i32, } -impl CmdLineContext { +impl<'a> CmdLineContext<'a> { fn get_lines(&self) -> LineContent { let content_line: Vec<(Option, Vec)> = self.content .iter() diff --git a/src/nvim/redraw_handler.rs b/src/nvim/redraw_handler.rs index 5258cb4..874dff5 100644 --- a/src/nvim/redraw_handler.rs +++ b/src/nvim/redraw_handler.rs @@ -110,6 +110,8 @@ pub fn call_gui_event( .ok_or_else(|| "Nvim not initialized".to_owned()) .and_then(|mut nvim| { nvim.set_option(UiOption::ExtCmdline(try_uint!(args[1]) == 1)) + .map_err(|e| e.to_string())?; + nvim.set_option(UiOption::ExtWildmenu(try_uint!(args[1]) == 1)) .map_err(|e| e.to_string()) })?, opt => error!("Unknown option {}", opt), @@ -229,6 +231,9 @@ pub fn call( "cmdline_block_hide" => ui.cmdline_block_hide(), "cmdline_pos" => call!(ui->cmdline_pos(args: uint, uint)), "cmdline_special_char" => call!(ui->cmdline_special_char(args: str, bool, uint)), + "wildmenu_show" => call!(ui->wildmenu_show(args: ext)), + "wildmenu_hide" => ui.wildmenu_hide(), + "wildmenu_select" => call!(ui->wildmenu_select(args: int)), _ => { warn!("Event {}({:?})", method, args); RepaintMode::Nothing diff --git a/src/popup_menu.rs b/src/popup_menu.rs index b8c678b..bbf03bf 100644 --- a/src/popup_menu.rs +++ b/src/popup_menu.rs @@ -1,6 +1,7 @@ use std::rc::Rc; use std::cell::RefCell; use std::cmp::min; +use std::iter; use gtk; use gtk::prelude::*; @@ -62,11 +63,17 @@ impl State { let info_label = gtk::Label::new(None); info_label.set_line_wrap(true); + let scroll = gtk::ScrolledWindow::new(None, None); + + tree.connect_size_allocate( + clone!(scroll, renderer => move |tree, _| on_treeview_allocate(&scroll, tree, &renderer)), + ); + State { nvim: None, tree, - scroll: gtk::ScrolledWindow::new(None, None), renderer, + scroll, css_provider, info_label, word_column, @@ -82,6 +89,7 @@ impl State { self.scroll.set_max_content_width(ctx.max_width); self.scroll.set_propagate_natural_width(true); + self.scroll.set_propagate_natural_height(true); self.update_tree(&ctx); self.select(ctx.selected); } @@ -99,27 +107,29 @@ impl State { let (word_max_width, _) = layout.get_pixel_size(); let word_column_width = word_max_width + xpad * 2 + DEFAULT_PADDING; - if kind_exists { layout.set_text("[v]"); let (kind_width, _) = layout.get_pixel_size(); - self.kind_column.set_fixed_width(kind_width + xpad * 2 + DEFAULT_PADDING); + self.kind_column + .set_fixed_width(kind_width + xpad * 2 + DEFAULT_PADDING); self.kind_column.set_visible(true); - self.word_column.set_fixed_width(min(max_width - kind_width, word_column_width)); + self.word_column + .set_fixed_width(min(max_width - kind_width, word_column_width)); } else { self.kind_column.set_visible(false); - self.word_column.set_fixed_width(min(max_width, word_column_width)); + self.word_column + .set_fixed_width(min(max_width, word_column_width)); } - let max_menu_line = ctx.menu_items.iter().max_by_key(|m| m.menu.len()).unwrap(); if max_menu_line.menu.len() > 0 { layout.set_text(max_menu_line.menu); let (menu_max_width, _) = layout.get_pixel_size(); - self.menu_column.set_fixed_width(menu_max_width + xpad * 2 + DEFAULT_PADDING); + self.menu_column + .set_fixed_width(menu_max_width + xpad * 2 + DEFAULT_PADDING); self.menu_column.set_visible(true); } else { self.menu_column.set_visible(false); @@ -133,16 +143,14 @@ impl State { self.limit_column_widths(ctx); - self.renderer.set_property_font( - Some(&ctx.font_ctx.font_description().to_string()), - ); + self.renderer + .set_property_font(Some(&ctx.font_ctx.font_description().to_string())); let color_model = &ctx.color_model; - self.renderer.set_property_foreground_rgba( - Some(&color_model.pmenu_fg().into()), - ); + self.renderer + .set_property_foreground_rgba(Some(&color_model.pmenu_fg().into())); - self.update_css(color_model); + update_css(&self.css_provider, color_model); let list_store = gtk::ListStore::new(&vec![gtk::Type::String; 4]); let all_column_ids: Vec = (0..4).map(|i| i as u32).collect(); @@ -152,42 +160,17 @@ impl State { list_store.insert_with_values(None, &all_column_ids, &line_array[..]); } - self.tree.set_model(Some(&list_store)); - } - - fn update_css(&self, color_model: &ColorModel) { - let bg = color_model.pmenu_bg_sel(); - let fg = color_model.pmenu_fg_sel(); - - match gtk::CssProviderExt::load_from_data( - &self.css_provider, - &format!( - ".view :selected {{ color: {}; background-color: {};}}\n - .view {{ background-color: {}; }}", - fg.to_hex(), - bg.to_hex(), - color_model.pmenu_bg().to_hex(), - ).as_bytes(), - ) { - Err(e) => error!("Can't update css {}", e), - Ok(_) => (), - }; + self.tree.set_model(&list_store); } fn select(&self, selected: i64) { if selected >= 0 { let selected_path = gtk::TreePath::new_from_string(&format!("{}", selected)); self.tree.get_selection().select_path(&selected_path); - self.tree.scroll_to_cell( - Some(&selected_path), - None, - false, - 0.0, - 0.0, - ); + self.tree + .scroll_to_cell(Some(&selected_path), None, false, 0.0, 0.0); self.show_info_column(&selected_path); - } else { self.tree.get_selection().unselect_all(); self.info_label.hide(); @@ -212,17 +195,6 @@ impl State { self.info_label.hide(); } } - - fn calc_treeview_height(&self) -> i32 { - let (_, natural_size) = self.renderer.get_preferred_height(&self.tree); - let (_, ypad) = self.renderer.get_padding(); - - let row_height = natural_size + ypad; - - let actual_count = self.tree.get_model().unwrap().iter_n_children(None); - - row_height * min(actual_count, MAX_VISIBLE_ROWS) as i32 - } } pub struct PopupMenu { @@ -243,11 +215,9 @@ impl PopupMenu { state.tree.set_headers_visible(false); state.tree.set_can_focus(false); - - state.scroll.set_policy( - gtk::PolicyType::Automatic, - gtk::PolicyType::Automatic, - ); + state + .scroll + .set_policy(gtk::PolicyType::Automatic, gtk::PolicyType::Automatic); state.scroll.add(&state.tree); state.scroll.show_all(); @@ -259,22 +229,17 @@ impl PopupMenu { let state = Rc::new(RefCell::new(state)); 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 nvim = state.nvim.as_ref().unwrap().nvim(); if let Some(mut nvim) = nvim { - tree_button_press(tree, ev, &mut *nvim) - } else { - Inhibit(false) + tree_button_press(tree, ev, &mut *nvim, ""); } - }, - ); - - let state_ref = state.clone(); - state.borrow().tree.connect_size_allocate(move |_, _| { - on_treeview_allocate(state_ref.clone()) - }); + Inhibit(false) + }); let state_ref = state.clone(); popover.connect_key_press_event(move |_, ev| { @@ -337,15 +302,24 @@ pub struct PopupMenuContext<'a> { pub max_width: i32, } -fn tree_button_press(tree: >k::TreeView, ev: &EventButton, nvim: &mut Neovim) -> Inhibit { +pub fn tree_button_press( + tree: >k::TreeView, + ev: &EventButton, + nvim: &mut Neovim, + last_command: &str, +) { if ev.get_event_type() != EventType::ButtonPress { - return Inhibit(false); + return; } let (paths, ..) = tree.get_selection().get_selected_rows(); let selected_idx = if !paths.is_empty() { let ids = paths[0].get_indices(); - if !ids.is_empty() { ids[0] } else { -1 } + if !ids.is_empty() { + ids[0] + } else { + -1 + } } else { -1 }; @@ -356,21 +330,20 @@ fn tree_button_press(tree: >k::TreeView, ev: &EventButton, nvim: &mut Neovim) let scroll_count = find_scroll_count(selected_idx, target_idx); - let mut apply_command = String::new(); - - for _ in 0..scroll_count { - if target_idx > selected_idx { - apply_command.push_str(""); - } else { - apply_command.push_str(""); - } - } - apply_command.push_str(""); + let mut apply_command: String = if target_idx > selected_idx { + (0..scroll_count) + .map(|_| "") + .chain(iter::once(last_command)) + .collect() + } else { + (0..scroll_count) + .map(|_| "") + .chain(iter::once(last_command)) + .collect() + }; nvim.input(&apply_command).report_err(); } - - Inhibit(false) } fn find_scroll_count(selected_idx: i32, target_idx: i32) -> i32 { @@ -383,22 +356,46 @@ fn find_scroll_count(selected_idx: i32, target_idx: i32) -> i32 { } } +fn on_treeview_allocate( + scroll: >k::ScrolledWindow, + tree: >k::TreeView, + renderer: >k::CellRendererText, +) { + let treeview_height = calc_treeview_height(tree, renderer); -fn on_treeview_allocate(state: Rc>) { - let treeview_height = state.borrow().calc_treeview_height(); - - idle_add(move || { - let state = state.borrow(); - - // strange solution to make gtk assertions happy - let previous_height = state.scroll.get_max_content_height(); - if previous_height < treeview_height { - state.scroll.set_max_content_height(treeview_height); - state.scroll.set_min_content_height(treeview_height); - } else if previous_height > treeview_height { - state.scroll.set_min_content_height(treeview_height); - state.scroll.set_max_content_height(treeview_height); - } + idle_add(clone!(scroll => move || { + scroll + .set_max_content_height(treeview_height); Continue(false) - }); + })); +} + +pub fn update_css(css_provider: >k::CssProvider, color_model: &ColorModel) { + let bg = color_model.pmenu_bg_sel(); + let fg = color_model.pmenu_fg_sel(); + + match gtk::CssProviderExt::load_from_data( + css_provider, + &format!( + ".view :selected {{ color: {}; background-color: {};}}\n + .view {{ background-color: {}; }}", + fg.to_hex(), + bg.to_hex(), + color_model.pmenu_bg().to_hex(), + ).as_bytes(), + ) { + Err(e) => error!("Can't update css {}", e), + Ok(_) => (), + }; +} + +pub fn calc_treeview_height(tree: >k::TreeView, renderer: >k::CellRendererText) -> i32 { + let (_, natural_size) = renderer.get_preferred_height(tree); + let (_, ypad) = renderer.get_padding(); + + let row_height = natural_size + ypad; + + let actual_count = tree.get_model().unwrap().iter_n_children(None); + + row_height * min(actual_count, MAX_VISIBLE_ROWS) as i32 } diff --git a/src/shell.rs b/src/shell.rs index c4971ba..c66c6f0 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1364,6 +1364,7 @@ impl State { let render_state = self.render_state.borrow(); let (x, y, width, height) = cursor.to_area(render_state.font_ctx.cell_metrics()); let ctx = CmdLineContext { + nvim: &self.nvim, content, pos, firstc, @@ -1421,6 +1422,22 @@ impl State { self.cmd_line.special_char(&*render_state, c, shift, level); RepaintMode::Nothing } + + pub fn wildmenu_show(&self, items: Vec) -> RepaintMode { + self.cmd_line + .show_wildmenu(items, &*self.render_state.borrow(), self.max_popup_width()); + RepaintMode::Nothing + } + + pub fn wildmenu_hide(&self) -> RepaintMode { + self.cmd_line.hide_wildmenu(); + RepaintMode::Nothing + } + + pub fn wildmenu_select(&self, selected: i64) -> RepaintMode { + self.cmd_line.wildmenu_select(selected); + RepaintMode::Nothing + } } impl CursorRedrawCb for State {