nvim_gtk/
tabline.rs

1use std::ops::Deref;
2use std::rc::Rc;
3use std::cell::RefCell;
4
5use gtk;
6use gtk::prelude::*;
7
8use glib;
9use glib::signal;
10
11use pango;
12
13use neovim_lib::{NeovimApi, NeovimApiAsync};
14use neovim_lib::neovim_api::Tabpage;
15
16use crate::nvim;
17use crate::nvim::ErrorReport;
18
19struct State {
20    data: Vec<Tabpage>,
21    selected: Option<Tabpage>,
22    nvim: Option<Rc<nvim::NeovimClient>>,
23}
24
25impl State {
26    pub fn new() -> Self {
27        State {
28            data: Vec::new(),
29            selected: None,
30            nvim: None,
31        }
32    }
33
34    fn switch_page(&self, idx: u32) {
35        let target = &self.data[idx as usize];
36        if Some(target) != self.selected.as_ref() {
37            if let Some(mut nvim) = self.nvim.as_ref().unwrap().nvim() {
38                nvim.set_current_tabpage(target).report_err();
39            }
40        }
41    }
42
43    fn close_tab(&self, idx: u32) {
44        if let Some(mut nvim) = self.nvim.as_ref().unwrap().nvim() {
45            nvim.command_async(&format!(":tabc {}", idx + 1))
46                .cb(|r| r.report_err())
47                .call();
48        }
49    }
50}
51
52pub struct Tabline {
53    tabs: gtk::Notebook,
54    state: Rc<RefCell<State>>,
55    switch_handler_id: glib::SignalHandlerId,
56}
57
58impl Tabline {
59    pub fn new() -> Self {
60        let tabs = gtk::Notebook::new();
61
62        tabs.set_can_focus(false);
63        tabs.set_scrollable(true);
64        tabs.set_show_border(false);
65        tabs.set_border_width(0);
66        tabs.set_hexpand(true);
67        tabs.hide();
68
69        let state = Rc::new(RefCell::new(State::new()));
70
71        let state_ref = state.clone();
72        let switch_handler_id =
73            tabs.connect_switch_page(move |_, _, idx| state_ref.borrow().switch_page(idx));
74
75        Tabline {
76            tabs,
77            state,
78            switch_handler_id,
79        }
80    }
81
82    fn update_state(
83        &self,
84        nvim: &Rc<nvim::NeovimClient>,
85        selected: &Tabpage,
86        tabs: &[(Tabpage, Option<String>)],
87    ) {
88        let mut state = self.state.borrow_mut();
89
90        if state.nvim.is_none() {
91            state.nvim = Some(nvim.clone());
92        }
93
94        state.selected = Some(selected.clone());
95
96        state.data = tabs.iter().map(|item| item.0.clone()).collect();
97    }
98
99    pub fn update_tabs(
100        &self,
101        nvim: &Rc<nvim::NeovimClient>,
102        selected: &Tabpage,
103        tabs: &[(Tabpage, Option<String>)],
104    ) {
105        if tabs.len() <= 1 {
106            self.tabs.hide();
107            return;
108        } else {
109            self.tabs.show();
110        }
111
112        self.update_state(nvim, selected, tabs);
113
114
115        signal::signal_handler_block(&self.tabs, &self.switch_handler_id);
116
117        let count = self.tabs.get_n_pages() as usize;
118        if count < tabs.len() {
119            for _ in count..tabs.len() {
120                let empty = gtk::Box::new(gtk::Orientation::Vertical, 0);
121                empty.show_all();
122                let title = gtk::Label::new(None);
123                title.set_ellipsize(pango::EllipsizeMode::Middle);
124                title.set_width_chars(25);
125                let close_btn = gtk::Button::new_from_icon_name(
126                    Some("window-close-symbolic"),
127                    gtk::IconSize::Menu,
128                );
129                close_btn.set_relief(gtk::ReliefStyle::None);
130                close_btn.get_style_context().add_class("small-button");
131                close_btn.set_focus_on_click(false);
132                let label_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
133                label_box.pack_start(&title, true, false, 0);
134                label_box.pack_start(&close_btn, false, false, 0);
135                title.show();
136                close_btn.show();
137                self.tabs.append_page(&empty, Some(&label_box));
138                self.tabs.set_child_tab_expand(&empty, true);
139
140                let tabs = self.tabs.clone();
141                let state_ref = Rc::clone(&self.state);
142                close_btn.connect_clicked(move |btn| {
143                    let current_label = btn
144                        .get_parent().unwrap();
145                    for i in 0..tabs.get_n_pages() {
146                        let page = tabs.get_nth_page(Some(i)).unwrap();
147                        let label = tabs.get_tab_label(&page).unwrap();
148                        if label == current_label {
149                            state_ref.borrow().close_tab(i);
150                        }
151                    }
152                });
153            }
154        } else if count > tabs.len() {
155            for _ in tabs.len()..count {
156                self.tabs.remove_page(None);
157            }
158        }
159
160        for (idx, tab) in tabs.iter().enumerate() {
161            let tab_child = self.tabs.get_nth_page(Some(idx as u32));
162            let tab_label = self.tabs
163                .get_tab_label(&tab_child.unwrap())
164                .unwrap()
165                .downcast::<gtk::Box>()
166                .unwrap()
167                .get_children()
168                .into_iter()
169                .next()
170                .unwrap()
171                .downcast::<gtk::Label>()
172                .unwrap();
173            tab_label.set_text(tab.1.as_ref().unwrap_or(&"??".to_owned()));
174
175            if *selected == tab.0 {
176                self.tabs.set_current_page(Some(idx as u32));
177            }
178        }
179
180        signal::signal_handler_unblock(&self.tabs, &self.switch_handler_id);
181    }
182}
183
184impl Deref for Tabline {
185    type Target = gtk::Notebook;
186
187    fn deref(&self) -> &gtk::Notebook {
188        &self.tabs
189    }
190}