use std::cmp::max;
use glib::{Cast, Object};
use glib::object::IsA;
use gtk;
use gtk::{
traits::{
CellRendererTextExt,
GtkListStoreExt,
ScrolledWindowExt,
TreeModelExt,
TreeSelectionExt,
TreeViewColumnExt,
TreeViewExt,
WidgetExt,
},
Align,
CellRendererText,
ListStore,
TreeIter,
TreeModel,
TreeViewColumn,
};
use gtk::PolicyType::{Automatic, Never};
use pango::EllipsizeMode;
use relm::{Relm, Widget};
use relm_derive::widget;
use app::COMMAND_MODE;
use completion::Completers;
use completion::Column::{self, Expand};
use self::Msg::*;
use super::{Completer, Completion, DEFAULT_COMPLETER_IDENT, NO_COMPLETER_IDENT};
const COMPLETION_VIEW_MAX_HEIGHT: i32 = 300;
#[allow(missing_docs)]
pub struct Model {
completion: Completion,
original_input: String,
relm: Relm<CompletionView>,
visible: bool,
}
pub type Mode = String;
pub type Text = String;
#[derive(Msg)]
pub enum Msg {
AddCompleters(Completers),
Completer(String),
CompletionChange(String),
DeleteCurrentCompletionItem,
SelectNext,
SelectPrevious,
SetOriginalInput(String),
ShowCompletion,
UpdateCompletions(Mode, Text, bool),
Visible(bool),
}
#[widget]
impl Widget for CompletionView {
fn add_completers(&mut self, completers: Completers) {
for (ident, completer) in completers {
self.model.completion.add_completer(ident, completer);
}
}
fn init_view(&mut self) {
self.add_columns(2);
}
fn model(relm: &Relm<Self>, completers: Completers) -> Model {
let mut completion = Completion::new();
completion.set_completers(completers);
Model {
completion,
original_input: String::new(),
relm: relm.clone(),
visible: false,
}
}
fn show_completion(&mut self) {
self.unselect();
self.scroll_to_first();
self.model.visible = true;
}
fn update(&mut self, msg: Msg) {
match msg {
AddCompleters(completers) => self.add_completers(completers),
Completer(completer) => self.set_completer(&completer, ""),
CompletionChange(_) => (),
DeleteCurrentCompletionItem => self.delete_current_completion_item(),
SelectNext => self.select_next(),
SelectPrevious => self.select_previous(),
SetOriginalInput(input) => self.set_original_input(&input),
ShowCompletion => self.show_completion(),
UpdateCompletions(mode, text, is_normal_command) =>
self.update_completions(&mode, &text, is_normal_command),
Visible(visible) => self.model.visible = visible,
}
}
view! {
#[name="scrolled_window"]
gtk::ScrolledWindow {
max_content_height: COMPLETION_VIEW_MAX_HEIGHT,
propagate_natural_height: true,
valign: Align::End,
visible: self.model.visible,
#[name="tree_view"]
gtk::TreeView {
can_focus: false,
enable_search: false,
headers_visible: false,
}
}
}
}
impl CompletionView {
fn add_column(&self, index: i32, foreground_index: i32, column: Column) {
let view_column = TreeViewColumn::new();
let cell = CellRendererText::new();
if column == Expand {
cell.set_ellipsize(EllipsizeMode::End);
view_column.set_expand(true);
}
view_column.pack_start(&cell, true);
view_column.add_attribute(&cell, "text", index);
view_column.add_attribute(&cell, "foreground", foreground_index);
self.widgets.tree_view.append_column(&view_column);
}
fn add_columns(&self, column_count: i32) {
self.remove_columns();
for i in 0 .. column_count {
self.add_column(i, column_count + i, Expand);
}
}
fn add_columns_from_completer(&self, completer: &dyn Completer) {
self.remove_columns();
let columns = completer.columns();
let column_count = columns.len() as i32;
for (i, column) in columns.iter().enumerate() {
let i = i as i32;
self.add_column(i, column_count + i, *column);
}
}
fn adjust_columns(&self, completer: &dyn Completer) {
self.add_columns_from_completer(completer);
}
fn adjust_policy<M: IsA<Object> + IsA<TreeModel>>(&self, model: &M) {
self.widgets.tree_view.set_model(Some(model));
let policy =
if model.iter_n_children(None) < 2 {
Never
}
else {
Automatic
};
self.widgets.scrolled_window.set_policy(Never, policy);
}
fn complete_result(&self) {
let selection = self.widgets.tree_view.selection();
if let Some(completion) = self.model.completion.complete_result(&selection) {
self.model.relm.stream().emit(CompletionChange(completion));
}
}
fn delete_current_completion_item(&self) {
if let Some((model, iter)) = self.widgets.tree_view.selection().selected() {
if let Ok(model) = model.downcast::<ListStore>() {
self.select_next();
model.remove(&iter);
self.adjust_policy(&model);
}
}
}
fn disable_scrollbars(&self) {
self.widgets.scrolled_window.set_policy(Never, Never);
}
fn filter(&mut self, command_entry_text: &str) {
self.disable_scrollbars();
let model = self.model.completion.filter(command_entry_text);
if let Some(model) = model {
self.adjust_policy(&model);
}
}
fn remove_columns(&self) {
for column in &self.widgets.tree_view.columns() {
self.widgets.tree_view.remove_column(column);
}
}
fn scroll(&self, model: &TreeModel, iter: &TreeIter) {
if let Some(path) = model.path(iter) {
self.widgets.tree_view.scroll_to_cell(Some(&path), None::<&TreeViewColumn>, false, 0.0, 0.0);
}
}
fn scroll_to_first(&self) {
if let Some(model) = self.widgets.tree_view.model() {
if let Some(iter) = model.iter_first() {
if let Some(path) = model.path(&iter) {
self.widgets.tree_view.scroll_to_cell(Some(&path), None::<&TreeViewColumn>, false, 0.0, 0.0);
}
}
}
}
fn select_completer(&mut self, command_entry_text: &str, is_normal_command: bool) {
let text = command_entry_text.trim_start();
let completer =
if let Some(space_index) = text.find(' ') {
&text[..space_index]
}
else if is_normal_command {
DEFAULT_COMPLETER_IDENT
}
else {
NO_COMPLETER_IDENT
};
self.set_completer(completer, command_entry_text);
}
fn select_next(&self) {
if let Some(model) = self.widgets.tree_view.model() {
let selection = self.widgets.tree_view.selection();
if let Some((model, selected_iter)) = selection.selected() {
if model.iter_next(&selected_iter) {
selection.select_iter(&selected_iter);
self.scroll(&model, &selected_iter);
}
else {
self.unselect();
self.model.relm.stream().emit(CompletionChange(self.model.original_input.clone()));
}
}
else if let Some(iter) = model.iter_first() {
self.scroll(&model, &iter);
selection.select_iter(&iter);
}
self.complete_result();
}
}
fn select_previous(&self) {
if let Some(model) = self.widgets.tree_view.model() {
let selection = self.widgets.tree_view.selection();
if let Some((model, selected_iter)) = selection.selected() {
if model.iter_previous(&selected_iter) {
selection.select_iter(&selected_iter);
self.scroll(&model, &selected_iter);
}
else {
self.unselect();
self.model.relm.stream().emit(CompletionChange(self.model.original_input.clone()));
}
}
else if let Some(iter) = model.iter_nth_child(None, max(0, model.iter_n_children(None) - 1)) {
self.scroll(&model, &iter);
selection.select_iter(&iter);
}
self.complete_result();
}
}
fn set_completer(&mut self, completer: &str, command_entry_text: &str) {
if self.model.completion.adjust_model(completer) {
let model: Option<&ListStore> = None;
self.widgets.tree_view.set_model(model);
}
{
let completer = self.model.completion.current_completer().expect("completer should be set");
self.adjust_columns(completer);
}
self.filter(command_entry_text);
}
fn set_original_input(&mut self, input: &str) {
self.model.original_input = input.to_string();
}
fn unselect(&self) {
let selection = self.widgets.tree_view.selection();
selection.unselect_all();
}
fn update_completions(&mut self, current_mode: &str, command_entry_text: &str, is_normal_command: bool) {
if current_mode == COMMAND_MODE {
self.select_completer(command_entry_text, is_normal_command);
}
else {
self.filter(command_entry_text);
}
if self.model.completion.current_completer_ident() != NO_COMPLETER_IDENT {
self.filter(command_entry_text);
self.set_original_input(command_entry_text);
}
self.unselect();
}
}