nvim_gtk/plug_manager/
ui.rs

1use std::cell::RefCell;
2use std::ops::Deref;
3use std::rc::Rc;
4use std::sync::Arc;
5
6use crate::ui::UiMutex;
7
8use gtk;
9use gtk::prelude::*;
10
11use super::manager;
12use super::plugin_settings_dlg;
13use super::store::{PlugInfo, Store};
14use super::vimawesome;
15use crate::nvim_config::NvimConfig;
16
17pub struct Ui<'a> {
18    manager: &'a Arc<UiMutex<manager::Manager>>,
19}
20
21impl<'a> Ui<'a> {
22    pub fn new(manager: &'a Arc<UiMutex<manager::Manager>>) -> Ui<'a> {
23        manager.borrow_mut().reload_store();
24
25        Ui { manager }
26    }
27
28    pub fn show<T: IsA<gtk::Window>>(&mut self, parent: &T) {
29        let dlg = gtk::Dialog::new_with_buttons(
30            Some("Plug"),
31            Some(parent),
32            gtk::DialogFlags::DESTROY_WITH_PARENT,
33            &[
34                ("Cancel", gtk::ResponseType::Cancel),
35                ("Ok", gtk::ResponseType::Ok),
36            ],
37        );
38
39        dlg.set_default_size(800, 600);
40        let content = dlg.get_content_area();
41
42        let header_bar = gtk::HeaderBar::new();
43
44        let add_plug_btn = gtk::Button::new_with_label("Add..");
45        add_plug_btn
46            .get_style_context()
47            .add_class("suggested-action");
48        header_bar.pack_end(&add_plug_btn);
49
50        let enable_swc = gtk::Switch::new();
51        enable_swc.set_valign(gtk::Align::Center);
52        enable_swc.show();
53
54        header_bar.pack_end(&enable_swc);
55
56        header_bar.set_title(Some("Plug"));
57        header_bar.set_show_close_button(true);
58        header_bar.show();
59
60        dlg.set_titlebar(Some(&header_bar));
61
62        let pages = SettingsPages::new(
63            clone!(add_plug_btn => move |row_name| if row_name == "plugins" {
64                add_plug_btn.show();
65            } else {
66                add_plug_btn.hide();
67            }),
68        );
69
70        enable_swc.set_state(self.manager.borrow().store.is_enabled());
71
72        let plugins = gtk::Box::new(gtk::Orientation::Vertical, 3);
73        let plugs_panel = self.fill_plugin_list(&plugins, &self.manager.borrow().store);
74
75        add_vimawesome_tab(&pages, &self.manager, &plugs_panel);
76
77        let plugins_lbl = gtk::Label::new(Some("Plugins"));
78        pages.add_page(&plugins_lbl, &plugins, "plugins");
79
80        add_help_tab(
81            &pages,
82            &format!(
83                "NeovimGtk plugin manager is a GUI for vim-plug.\n\
84            It can load plugins from vim-plug configuration if vim-plug sarted and NeovimGtk manager settings is empty.\n\
85            When enabled it generate and load vim-plug as simple vim file at startup before init.vim is processed.\n\
86            So <b>after</b> enabling this manager <b>you must disable vim-plug</b> configuration in init.vim.\n\
87            This manager currently only manage vim-plug configuration and do not any actions on plugin management.\n\
88            So you must call all vim-plug (PlugInstall, PlugUpdate, PlugClean) commands manually.\n\
89            Current configuration source is <b>{}</b>",
90                match self.manager.borrow().plug_manage_state {
91                    manager::PlugManageState::NvimGtk => "NeovimGtk config file",
92                    manager::PlugManageState::VimPlug => "loaded from vim-plug",
93                    manager::PlugManageState::Unknown => "Unknown",
94                }
95            ),
96        );
97
98        let manager_ref = self.manager.clone();
99        enable_swc.connect_state_set(move |_, state| {
100            manager_ref.borrow_mut().store.set_enabled(state);
101            Inhibit(false)
102        });
103
104        let manager_ref = self.manager.clone();
105        add_plug_btn.connect_clicked(clone!(dlg => move |_| {
106            show_add_plug_dlg(&dlg, &manager_ref, &plugs_panel);
107        }));
108
109        content.pack_start(&*pages, true, true, 0);
110        content.show_all();
111
112        if dlg.run() == gtk::ResponseType::Ok {
113            let mut manager = self.manager.borrow_mut();
114            manager.clear_removed();
115            manager.save();
116            if let Some(config_path) = NvimConfig::new(manager.generate_config()).generate_config()
117            {
118                if let Some(path) = config_path.to_str() {
119                    manager.vim_plug.reload(path);
120                }
121            }
122        }
123
124        dlg.destroy();
125    }
126
127    fn fill_plugin_list(&self, panel: &gtk::Box, store: &Store) -> gtk::ListBox {
128        let scroll = gtk::ScrolledWindow::new(
129            Option::<&gtk::Adjustment>::None,
130            Option::<&gtk::Adjustment>::None,
131        );
132        scroll.get_style_context().add_class("view");
133        let plugs_panel = gtk::ListBox::new();
134
135        for (idx, plug_info) in store.get_plugs().iter().enumerate() {
136            let row = create_plug_row(idx, plug_info, &self.manager);
137
138            plugs_panel.add(&row);
139        }
140
141        scroll.add(&plugs_panel);
142        panel.pack_start(&scroll, true, true, 5);
143
144        panel.pack_start(
145            &create_up_down_btns(&plugs_panel, &self.manager),
146            false,
147            true,
148            0,
149        );
150
151        plugs_panel
152    }
153}
154
155fn create_up_down_btns(
156    plugs_panel: &gtk::ListBox,
157    manager: &Arc<UiMutex<manager::Manager>>,
158) -> gtk::Box {
159    let buttons_panel = gtk::Box::new(gtk::Orientation::Horizontal, 5);
160    let up_btn = gtk::Button::new_from_icon_name(Some("go-up-symbolic"), gtk::IconSize::Button);
161    let down_btn = gtk::Button::new_from_icon_name(Some("go-down-symbolic"), gtk::IconSize::Button);
162
163    up_btn.connect_clicked(clone!(plugs_panel, manager => move |_| {
164        if let Some(row) = plugs_panel.get_selected_row() {
165            let idx = row.get_index();
166            if idx > 0 {
167                plugs_panel.unselect_row(&row);
168                plugs_panel.remove(&row);
169                plugs_panel.insert(&row, idx - 1);
170                plugs_panel.select_row(Some(&row));
171                manager.borrow_mut().move_item(idx as usize, -1);
172            }
173        }
174    }));
175
176    down_btn.connect_clicked(clone!(plugs_panel, manager => move |_| {
177        if let Some(row) = plugs_panel.get_selected_row() {
178            let idx = row.get_index();
179            let mut manager = manager.borrow_mut();
180            if idx >= 0 && idx < manager.store.plugs_count() as i32 {
181                plugs_panel.unselect_row(&row);
182                plugs_panel.remove(&row);
183                plugs_panel.insert(&row, idx + 1);
184                plugs_panel.select_row(Some(&row));
185                manager.move_item(idx as usize, 1);
186            }
187        }
188    }));
189
190    buttons_panel.pack_start(&up_btn, false, true, 0);
191    buttons_panel.pack_start(&down_btn, false, true, 0);
192    buttons_panel.set_halign(gtk::Align::Center);
193
194    buttons_panel
195}
196
197fn populate_get_plugins(
198    query: Option<String>,
199    get_plugins: &gtk::Box,
200    manager: Arc<UiMutex<manager::Manager>>,
201    plugs_panel: gtk::ListBox,
202) {
203    let plugs_panel = UiMutex::new(plugs_panel);
204    let get_plugins = UiMutex::new(get_plugins.clone());
205    vimawesome::call(query, move |res| {
206        let panel = get_plugins.borrow();
207        for child in panel.get_children() {
208            panel.remove(&child);
209        }
210        match res {
211            Ok(list) => {
212                let result = vimawesome::build_result_panel(&list, move |new_plug| {
213                    add_plugin(&manager, &*plugs_panel.borrow(), new_plug);
214                });
215                panel.pack_start(&result, true, true, 0);
216            }
217            Err(e) => {
218                panel.pack_start(&gtk::Label::new(Some(format!("{}", e).as_str())), false, true, 0);
219                error!("{}", e)
220            }
221        }
222    });
223}
224
225fn create_plug_row(
226    plug_idx: usize,
227    plug_info: &PlugInfo,
228    manager: &Arc<UiMutex<manager::Manager>>,
229) -> gtk::ListBoxRow {
230    let row = gtk::ListBoxRow::new();
231    let row_container = gtk::Box::new(gtk::Orientation::Vertical, 5);
232    row_container.set_border_width(5);
233    let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
234    let label_box = create_plug_label(plug_info);
235
236    let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
237    button_box.set_halign(gtk::Align::End);
238
239    let exists_button_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
240
241    let remove_btn = gtk::Button::new_with_label("Remove");
242    exists_button_box.pack_start(&remove_btn, false, true, 0);
243
244    let undo_btn = gtk::Button::new_with_label("Undo");
245
246    row_container.pack_start(&hbox, true, true, 0);
247    hbox.pack_start(&label_box, true, true, 0);
248    button_box.pack_start(&exists_button_box, false, true, 0);
249    hbox.pack_start(&button_box, false, true, 0);
250
251    row.add(&row_container);
252
253    remove_btn.connect_clicked(
254        clone!(manager, label_box, button_box, exists_button_box, undo_btn => move |_| {
255            label_box.set_sensitive(false);
256            button_box.remove(&exists_button_box);
257            button_box.pack_start(&undo_btn, false, true, 0);
258            button_box.show_all();
259            manager.borrow_mut().store.remove_plug(plug_idx);
260        }),
261    );
262
263    undo_btn.connect_clicked(
264        clone!(manager, label_box, button_box, exists_button_box, undo_btn => move |_| {
265            label_box.set_sensitive(true);
266            button_box.remove(&undo_btn);
267            button_box.pack_start(&exists_button_box, false, true, 0);
268            button_box.show_all();
269            manager.borrow_mut().store.restore_plug(plug_idx);
270        }),
271    );
272
273    row
274}
275
276fn show_add_plug_dlg<F: IsA<gtk::Window>>(
277    parent: &F,
278    manager: &Arc<UiMutex<manager::Manager>>,
279    plugs_panel: &gtk::ListBox,
280) {
281    if let Some(new_plugin) = plugin_settings_dlg::Builder::new("Add plugin").show(parent) {
282        add_plugin(manager, plugs_panel, new_plugin);
283    }
284}
285
286fn add_plugin(
287    manager: &Arc<UiMutex<manager::Manager>>,
288    plugs_panel: &gtk::ListBox,
289    new_plugin: PlugInfo,
290) -> bool {
291    let row = create_plug_row(manager.borrow().store.plugs_count(), &new_plugin, manager);
292
293    if manager.borrow_mut().add_plug(new_plugin) {
294        row.show_all();
295        plugs_panel.add(&row);
296        true
297    } else {
298        let dlg = gtk::MessageDialog::new(
299            None::<&gtk::Window>,
300            gtk::DialogFlags::empty(),
301            gtk::MessageType::Error,
302            gtk::ButtonsType::Ok,
303            "Plugin with this name or path already exists",
304        );
305        dlg.run();
306        dlg.destroy();
307        false
308    }
309}
310
311fn create_plug_label(plug_info: &PlugInfo) -> gtk::Box {
312    let label_box = gtk::Box::new(gtk::Orientation::Vertical, 5);
313
314    let name_lbl = gtk::Label::new(None);
315    name_lbl.set_markup(&format!("<b>{}</b>", plug_info.name));
316    name_lbl.set_halign(gtk::Align::Start);
317    let url_lbl = gtk::Label::new(Some(plug_info.get_plug_path().as_str()));
318    url_lbl.set_halign(gtk::Align::Start);
319
320    label_box.pack_start(&name_lbl, true, true, 0);
321    label_box.pack_start(&url_lbl, true, true, 0);
322    label_box
323}
324
325fn add_vimawesome_tab(
326    pages: &SettingsPages,
327    manager: &Arc<UiMutex<manager::Manager>>,
328    plugs_panel: &gtk::ListBox,
329) {
330    let get_plugins = gtk::Box::new(gtk::Orientation::Vertical, 0);
331    let spinner = gtk::Spinner::new();
332    let get_plugins_lbl = gtk::Label::new(Some("Get Plugins"));
333    pages.add_page(&get_plugins_lbl, &get_plugins, "get_plugins");
334
335    let list_panel = gtk::Box::new(gtk::Orientation::Vertical, 0);
336    let link_button = gtk::Label::new(None);
337    link_button.set_markup(
338        "Plugins are taken from: <a href=\"https://vimawesome.com\">https://vimawesome.com</a>",
339    );
340    let search_entry = gtk::SearchEntry::new();
341
342    get_plugins.pack_start(&link_button, false, true, 10);
343    get_plugins.pack_start(&search_entry, false, true, 5);
344    get_plugins.pack_start(&list_panel, true, true, 0);
345    list_panel.pack_start(&spinner, true, true, 0);
346    spinner.start();
347
348    search_entry.connect_activate(clone!(list_panel, manager, plugs_panel => move |se| {
349        let spinner = gtk::Spinner::new();
350        list_panel.pack_start(&spinner, false, true, 5);
351        spinner.show();
352        spinner.start();
353        populate_get_plugins(se.get_text().map(Into::into), &list_panel, manager.clone(), plugs_panel.clone());
354    }));
355
356    gtk::idle_add(clone!(manager, plugs_panel => move || {
357        populate_get_plugins(None, &list_panel, manager.clone(), plugs_panel.clone());
358        Continue(false)
359    }));
360}
361
362fn add_help_tab(pages: &SettingsPages, markup: &str) {
363    let help = gtk::Box::new(gtk::Orientation::Vertical, 3);
364    let label = gtk::Label::new(None);
365    label.set_markup(markup);
366    help.pack_start(&label, true, false, 0);
367
368    let help_lbl = gtk::Label::new(Some("Help"));
369    pages.add_page(&help_lbl, &help, "help");
370}
371
372struct SettingsPages {
373    categories: gtk::ListBox,
374    stack: gtk::Stack,
375    content: gtk::Box,
376    rows: Rc<RefCell<Vec<(gtk::ListBoxRow, &'static str)>>>,
377}
378
379impl SettingsPages {
380    pub fn new<F: Fn(&str) + 'static>(row_selected: F) -> Self {
381        let content = gtk::Box::new(gtk::Orientation::Horizontal, 5);
382        let categories = gtk::ListBox::new();
383        categories.get_style_context().add_class("view");
384        let stack = gtk::Stack::new();
385        stack.set_transition_type(gtk::StackTransitionType::Crossfade);
386        let rows: Rc<RefCell<Vec<(gtk::ListBoxRow, &'static str)>>> =
387            Rc::new(RefCell::new(Vec::new()));
388
389        content.pack_start(&categories, false, true, 0);
390        content.pack_start(&stack, true, true, 0);
391
392        categories.connect_row_selected(
393            clone!(stack, rows => move |_, row| if let Some(row) = row {
394                if let Some(ref r) = rows.borrow().iter().find(|r| r.0 == *row) {
395                    if let Some(child) = stack.get_child_by_name(&r.1) {
396                        stack.set_visible_child(&child);
397                        row_selected(&r.1);
398                    }
399
400                }
401            }),
402        );
403
404        SettingsPages {
405            categories,
406            stack,
407            content,
408            rows,
409        }
410    }
411
412    fn add_page<W: gtk::IsA<gtk::Widget>>(
413        &self,
414        label: &gtk::Label,
415        widget: &W,
416        name: &'static str,
417    ) {
418        let row = gtk::ListBoxRow::new();
419
420        let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0);
421        hbox.set_border_width(12);
422        hbox.pack_start(label, false, true, 0);
423        row.add(&hbox);
424
425        self.categories.add(&row);
426        self.stack.add_named(widget, name);
427        self.rows.borrow_mut().push((row, name));
428    }
429}
430
431impl Deref for SettingsPages {
432    type Target = gtk::Box;
433
434    fn deref(&self) -> &gtk::Box {
435        &self.content
436    }
437}