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: >k::Box, store: &Store) -> gtk::ListBox {
128 let scroll = gtk::ScrolledWindow::new(
129 Option::<>k::Adjustment>::None,
130 Option::<>k::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: >k::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: >k::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(>k::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: >k::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: >k::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::<>k::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: >k::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: >k::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) -> >k::Box {
435 &self.content
436 }
437}