nvim_gtk/
cmd_line.rs

1use std::cell::RefCell;
2use std::cmp::{max, min};
3use std::iter;
4use std::rc::Rc;
5use std::sync::Arc;
6
7use cairo;
8use gtk;
9use gtk::prelude::*;
10use pango;
11
12use unicode_segmentation::UnicodeSegmentation;
13
14use crate::cursor;
15use crate::highlight::{Highlight, HighlightMap};
16use crate::mode;
17use crate::nvim::{self, NeovimClient};
18use crate::popup_menu;
19use crate::render::{self, CellMetrics};
20use crate::shell;
21use crate::ui::UiMutex;
22use crate::ui_model::ModelLayout;
23
24pub struct Level {
25    model_layout: ModelLayout,
26    prompt_offset: usize,
27    preferred_width: i32,
28    preferred_height: i32,
29}
30
31impl Level {
32    pub fn insert(&mut self, c: String, shift: bool, render_state: &shell::RenderState) {
33        self.model_layout
34            .insert_char(c, shift, render_state.hl.default_hl());
35        self.update_preferred_size(render_state);
36    }
37
38    pub fn replace_from_ctx(&mut self, ctx: &CmdLineContext, render_state: &shell::RenderState) {
39        let content = ctx.get_lines(&render_state.hl);
40        self.replace_line(content.lines, false);
41        self.prompt_offset = content.prompt_offset;
42        self.model_layout
43            .set_cursor(self.prompt_offset + ctx.pos as usize);
44        self.update_preferred_size(render_state);
45    }
46
47    pub fn from_ctx(ctx: &CmdLineContext, render_state: &shell::RenderState) -> Self {
48        let content = ctx.get_lines(&render_state.hl);
49        let mut level = Level::from_lines(content.lines, ctx.max_width, render_state);
50
51        level.prompt_offset = content.prompt_offset;
52        level
53            .model_layout
54            .set_cursor(level.prompt_offset + ctx.pos as usize);
55        level.update_preferred_size(render_state);
56
57        level
58    }
59
60    fn replace_line(&mut self, lines: Vec<Vec<(Rc<Highlight>, Vec<String>)>>, append: bool) {
61        if append {
62            self.model_layout.layout_append(lines);
63        } else {
64            self.model_layout.layout(lines);
65        }
66    }
67
68    fn update_preferred_size(&mut self, render_state: &shell::RenderState) {
69        let &CellMetrics {
70            line_height,
71            char_width,
72            ..
73        } = render_state.font_ctx.cell_metrics();
74
75        let (columns, rows) = self.model_layout.size();
76        let columns = max(columns, 5);
77
78        self.preferred_width = (char_width * columns as f64) as i32;
79        self.preferred_height = (line_height * rows as f64) as i32;
80    }
81
82    pub fn from_multiline_content(
83        content: &Vec<Vec<(u64, String)>>,
84        max_width: i32,
85        render_state: &shell::RenderState,
86    ) -> Self {
87        let lines = content.to_attributed_content(&render_state.hl);
88        Level::from_lines(lines, max_width, render_state)
89    }
90
91    pub fn from_lines(
92        lines: Vec<Vec<(Rc<Highlight>, Vec<String>)>>,
93        max_width: i32,
94        render_state: &shell::RenderState,
95    ) -> Self {
96        let &CellMetrics { char_width, .. } = render_state.font_ctx.cell_metrics();
97
98        let max_width_chars = (max_width as f64 / char_width) as u64;
99
100        let mut model_layout = ModelLayout::new(max_width_chars);
101        model_layout.layout(lines);
102
103        let mut level = Level {
104            model_layout,
105            preferred_width: -1,
106            preferred_height: -1,
107            prompt_offset: 0,
108        };
109
110        level.update_preferred_size(render_state);
111        level
112    }
113
114    fn update_cache(&mut self, render_state: &shell::RenderState) {
115        render::shape_dirty(
116            &render_state.font_ctx,
117            &mut self.model_layout.model,
118            &render_state.hl,
119        );
120    }
121
122    fn set_cursor(&mut self, render_state: &shell::RenderState, pos: usize) {
123        self.model_layout.set_cursor(self.prompt_offset + pos);
124        self.update_preferred_size(render_state);
125    }
126}
127
128fn prompt_lines(
129    firstc: &str,
130    prompt: &str,
131    indent: u64,
132    hl: &HighlightMap,
133) -> (usize, Vec<(Rc<Highlight>, Vec<String>)>) {
134    let prompt: Vec<(Rc<Highlight>, Vec<String>)> = if !firstc.is_empty() {
135        if firstc.len() >= indent as usize {
136            vec![(hl.default_hl(), vec![firstc.to_owned()])]
137        } else {
138            vec![(
139                hl.default_hl(),
140                iter::once(firstc.to_owned())
141                    .chain((firstc.len()..indent as usize).map(|_| " ".to_owned()))
142                    .collect(),
143            )]
144        }
145    } else if !prompt.is_empty() {
146        prompt
147            .lines()
148            .map(|l| {
149                (
150                    hl.default_hl(),
151                    l.graphemes(true).map(|g| g.to_owned()).collect(),
152                )
153            })
154            .collect()
155    } else {
156        vec![]
157    };
158
159    let prompt_offset = prompt.last().map(|l| l.1.len()).unwrap_or(0);
160
161    (prompt_offset, prompt)
162}
163
164struct State {
165    nvim: Option<Rc<nvim::NeovimClient>>,
166    levels: Vec<Level>,
167    block: Option<Level>,
168    render_state: Rc<RefCell<shell::RenderState>>,
169    drawing_area: gtk::DrawingArea,
170    cursor: Option<cursor::BlinkCursor<State>>,
171}
172
173impl State {
174    fn new(drawing_area: gtk::DrawingArea, render_state: Rc<RefCell<shell::RenderState>>) -> Self {
175        State {
176            nvim: None,
177            levels: Vec::new(),
178            block: None,
179            render_state,
180            drawing_area,
181            cursor: None,
182        }
183    }
184
185    fn request_area_size(&self) {
186        let drawing_area = self.drawing_area.clone();
187        let block = self.block.as_ref();
188        let level = self.levels.last();
189
190        let (block_width, block_height) = block
191            .map(|b| (b.preferred_width, b.preferred_height))
192            .unwrap_or((0, 0));
193        let (level_width, level_height) = level
194            .map(|l| (l.preferred_width, l.preferred_height))
195            .unwrap_or((0, 0));
196
197        drawing_area.set_size_request(
198            max(level_width, block_width),
199            max(block_height + level_height, 40),
200        );
201    }
202
203    fn preferred_height(&self) -> i32 {
204        let level = self.levels.last();
205        level.map(|l| l.preferred_height).unwrap_or(0)
206            + self.block.as_ref().map(|b| b.preferred_height).unwrap_or(0)
207    }
208
209    fn set_cursor(&mut self, render_state: &shell::RenderState, pos: usize, level: usize) {
210        debug_assert!(level > 0);
211
212        // queue old cursor position
213        self.queue_redraw_cursor();
214
215        if let Some(l) = self.levels.get_mut(level - 1) {
216            l.set_cursor(render_state, pos)
217        }
218    }
219
220    fn queue_redraw_cursor(&mut self) {
221        if let Some(ref level) = self.levels.last() {
222            let level_preferred_height = level.preferred_height;
223            let block_preferred_height =
224                self.block.as_ref().map(|b| b.preferred_height).unwrap_or(0);
225
226            let gap = self.drawing_area.get_allocated_height()
227                - level_preferred_height
228                - block_preferred_height;
229
230            let model = &level.model_layout.model;
231
232            let mut cur_point = model.cur_point();
233            cur_point.extend_by_items(Some(model));
234
235            let render_state = self.render_state.borrow();
236            let cell_metrics = render_state.font_ctx.cell_metrics();
237
238            let (x, y, width, height) = cur_point.to_area_extend_ink(Some(model), cell_metrics);
239
240            if gap > 0 {
241                self.drawing_area
242                    .queue_draw_area(x, y + gap / 2, width, height);
243            } else {
244                self.drawing_area
245                    .queue_draw_area(x, y + block_preferred_height, width, height);
246            }
247        }
248    }
249}
250
251impl cursor::CursorRedrawCb for State {
252    fn queue_redraw_cursor(&mut self) {
253        self.queue_redraw_cursor();
254    }
255}
256
257pub struct CmdLine {
258    popover: gtk::Popover,
259    wild_tree: gtk::TreeView,
260    wild_scroll: gtk::ScrolledWindow,
261    wild_css_provider: gtk::CssProvider,
262    wild_renderer: gtk::CellRendererText,
263    wild_column: gtk::TreeViewColumn,
264    displyed: bool,
265    state: Arc<UiMutex<State>>,
266}
267
268impl CmdLine {
269    pub fn new(drawing: &gtk::DrawingArea, render_state: Rc<RefCell<shell::RenderState>>) -> Self {
270        let popover = gtk::Popover::new(Some(drawing));
271        popover.set_modal(false);
272        popover.set_position(gtk::PositionType::Right);
273
274        let content = gtk::Box::new(gtk::Orientation::Vertical, 0);
275
276        let drawing_area = gtk::DrawingArea::new();
277        content.pack_start(&drawing_area, true, true, 0);
278
279        let state = Arc::new(UiMutex::new(State::new(drawing_area.clone(), render_state)));
280        let weak_cb = Arc::downgrade(&state);
281        let cursor = cursor::BlinkCursor::new(weak_cb);
282        state.borrow_mut().cursor = Some(cursor);
283
284        drawing_area.connect_draw(clone!(state => move |_, ctx| gtk_draw(ctx, &state)));
285
286        let (wild_scroll, wild_tree, wild_css_provider, wild_renderer, wild_column) =
287            CmdLine::create_widlmenu(&state);
288        content.pack_start(&wild_scroll, false, true, 0);
289        popover.add(&content);
290
291        drawing_area.show_all();
292        content.show();
293
294        CmdLine {
295            popover,
296            state,
297            displyed: false,
298            wild_scroll,
299            wild_tree,
300            wild_css_provider,
301            wild_renderer,
302            wild_column,
303        }
304    }
305
306    fn create_widlmenu(
307        state: &Arc<UiMutex<State>>,
308    ) -> (
309        gtk::ScrolledWindow,
310        gtk::TreeView,
311        gtk::CssProvider,
312        gtk::CellRendererText,
313        gtk::TreeViewColumn,
314    ) {
315        let css_provider = gtk::CssProvider::new();
316
317        let tree = gtk::TreeView::new();
318        let style_context = tree.get_style_context();
319        style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
320
321        tree.get_selection().set_mode(gtk::SelectionMode::Single);
322        tree.set_headers_visible(false);
323        tree.set_can_focus(false);
324
325        let renderer = gtk::CellRendererText::new();
326        renderer.set_property_ellipsize(pango::EllipsizeMode::End);
327
328        let column = gtk::TreeViewColumn::new();
329        column.pack_start(&renderer, true);
330        column.add_attribute(&renderer, "text", 0);
331        tree.append_column(&column);
332
333        let scroll = gtk::ScrolledWindow::new(
334            Option::<&gtk::Adjustment>::None,
335            Option::<&gtk::Adjustment>::None,
336        );
337        scroll.set_propagate_natural_height(true);
338        scroll.set_propagate_natural_width(true);
339
340        scroll.add(&tree);
341
342        tree.connect_button_press_event(clone!(state => move |tree, ev| {
343            let state = state.borrow();
344            let nvim = state.nvim.as_ref().unwrap().nvim();
345            if let Some(mut nvim) = nvim {
346                popup_menu::tree_button_press(tree, ev, &mut *nvim, "");
347            }
348            Inhibit(false)
349        }));
350
351        (scroll, tree, css_provider, renderer, column)
352    }
353
354    pub fn show_level(&mut self, ctx: &CmdLineContext) {
355        let mut state = self.state.borrow_mut();
356        if state.nvim.is_none() {
357            state.nvim = Some(ctx.nvim.clone());
358        }
359        let render_state = state.render_state.clone();
360        let render_state = render_state.borrow();
361
362        if ctx.level_idx as usize == state.levels.len() {
363            let level = state.levels.last_mut().unwrap();
364            level.replace_from_ctx(ctx, &*render_state);
365            level.update_cache(&*render_state);
366        } else {
367            let mut level = Level::from_ctx(ctx, &*render_state);
368            level.update_cache(&*render_state);
369            state.levels.push(level);
370        }
371
372        state.request_area_size();
373
374        if !self.displyed {
375            self.displyed = true;
376            self.popover.set_pointing_to(&gtk::Rectangle {
377                x: ctx.x,
378                y: ctx.y,
379                width: ctx.width,
380                height: ctx.height,
381            });
382
383            self.popover.popup();
384            state.cursor.as_mut().unwrap().start();
385        } else {
386            state.drawing_area.queue_draw()
387        }
388    }
389
390    pub fn special_char(
391        &self,
392        render_state: &shell::RenderState,
393        c: String,
394        shift: bool,
395        level: u64,
396    ) {
397        let mut state = self.state.borrow_mut();
398
399        if let Some(level) = state.levels.get_mut((level - 1) as usize) {
400            level.insert(c, shift, render_state);
401            level.update_cache(&*render_state);
402        } else {
403            error!("Level {} does not exists", level);
404        }
405
406        state.request_area_size();
407        state.drawing_area.queue_draw()
408    }
409
410    pub fn hide_level(&mut self, level_idx: u64) {
411        let mut state = self.state.borrow_mut();
412
413        if level_idx as usize == state.levels.len() {
414            state.levels.pop();
415        }
416
417        if state.levels.is_empty() {
418            self.popover.hide();
419            self.displyed = false;
420            state.cursor.as_mut().unwrap().leave_focus();
421        }
422    }
423
424    pub fn show_block(&mut self, content: &Vec<Vec<(u64, String)>>, max_width: i32) {
425        let mut state = self.state.borrow_mut();
426        let mut block =
427            Level::from_multiline_content(content, max_width, &*state.render_state.borrow());
428        block.update_cache(&*state.render_state.borrow());
429        state.block = Some(block);
430        state.request_area_size();
431    }
432
433    pub fn block_append(&mut self, content: &Vec<(u64, String)>) {
434        let mut state = self.state.borrow_mut();
435        let render_state = state.render_state.clone();
436        {
437            let attr_content = content.to_attributed_content(&render_state.borrow().hl);
438
439            let block = state.block.as_mut().unwrap();
440            block.replace_line(attr_content, true);
441            block.update_preferred_size(&*render_state.borrow());
442            block.update_cache(&*render_state.borrow());
443        }
444        state.request_area_size();
445    }
446
447    pub fn block_hide(&self) {
448        self.state.borrow_mut().block = None;
449    }
450
451    pub fn pos(&self, render_state: &shell::RenderState, pos: u64, level: u64) {
452        self.state
453            .borrow_mut()
454            .set_cursor(render_state, pos as usize, level as usize);
455    }
456
457    pub fn set_mode_info(&self, mode_info: Option<mode::ModeInfo>) {
458        self.state
459            .borrow_mut()
460            .cursor
461            .as_mut()
462            .unwrap()
463            .set_mode_info(mode_info);
464    }
465
466    pub fn show_wildmenu(
467        &self,
468        items: Vec<String>,
469        render_state: &shell::RenderState,
470        max_width: i32,
471    ) {
472        // update font/color
473        self.wild_renderer.set_property_font(Some(
474            render_state
475                .font_ctx
476                .font_description()
477                .to_string()
478                .as_str(),
479        ));
480
481        self.wild_renderer
482            .set_property_foreground_rgba(Some(&render_state.hl.pmenu_fg().into()));
483
484        popup_menu::update_css(&self.wild_css_provider, &render_state.hl);
485
486        // set width
487        // this calculation produce width more then needed, but this is looks ok :)
488        let max_item_width = (items.iter().map(|item| item.len()).max().unwrap() as f64
489            * render_state.font_ctx.cell_metrics().char_width) as i32
490            + self.state.borrow().levels.last().unwrap().preferred_width;
491        self.wild_column
492            .set_fixed_width(min(max_item_width, max_width));
493        self.wild_scroll.set_max_content_width(max_width);
494
495        // load data
496        let list_store = gtk::ListStore::new(&[gtk::Type::String; 1]);
497        for item in items {
498            list_store.insert_with_values(None, &[0], &[&item]);
499        }
500        self.wild_tree.set_model(Some(&list_store));
501
502        // set height
503        let treeview_height =
504            popup_menu::calc_treeview_height(&self.wild_tree, &self.wild_renderer);
505
506        self.wild_scroll.set_max_content_height(treeview_height);
507
508        self.wild_scroll.show_all();
509    }
510
511    pub fn hide_wildmenu(&self) {
512        self.wild_scroll.hide();
513    }
514
515    pub fn wildmenu_select(&self, selected: i64) {
516        if selected >= 0 {
517            let wild_tree = self.wild_tree.clone();
518            idle_add(move || {
519                let selected_path = gtk::TreePath::new_from_string(&format!("{}", selected));
520                wild_tree.get_selection().select_path(&selected_path);
521                wild_tree.scroll_to_cell(Some(&selected_path), Option::<&gtk::TreeViewColumn>::None, false, 0.0, 0.0);
522
523                Continue(false)
524            });
525        } else {
526            self.wild_tree.get_selection().unselect_all();
527        }
528    }
529}
530
531fn gtk_draw(ctx: &cairo::Context, state: &Arc<UiMutex<State>>) -> Inhibit {
532    let state = state.borrow();
533    let preferred_height = state.preferred_height();
534    let level = state.levels.last();
535    let block = state.block.as_ref();
536
537    let render_state = state.render_state.borrow();
538
539    ctx.push_group();
540
541    render::fill_background(ctx, &render_state.hl, None);
542
543    let gap = state.drawing_area.get_allocated_height() - preferred_height;
544    if gap > 0 {
545        ctx.translate(0.0, (gap / 2) as f64);
546    }
547
548    if let Some(block) = block {
549        render::render(
550            ctx,
551            &cursor::EmptyCursor::new(),
552            &render_state.font_ctx,
553            &block.model_layout.model,
554            &render_state.hl,
555            None,
556        );
557
558        ctx.translate(0.0, block.preferred_height as f64);
559    }
560
561    if let Some(level) = level {
562        render::render(
563            ctx,
564            state.cursor.as_ref().unwrap(),
565            &render_state.font_ctx,
566            &level.model_layout.model,
567            &render_state.hl,
568            None,
569        );
570    }
571
572    ctx.pop_group_to_source();
573    ctx.paint();
574
575    Inhibit(false)
576}
577
578pub struct CmdLineContext<'a> {
579    pub nvim: &'a Rc<NeovimClient>,
580    pub content: Vec<(u64, String)>,
581    pub pos: u64,
582    pub firstc: String,
583    pub prompt: String,
584    pub indent: u64,
585    pub level_idx: u64,
586    pub x: i32,
587    pub y: i32,
588    pub width: i32,
589    pub height: i32,
590    pub max_width: i32,
591}
592
593impl<'a> CmdLineContext<'a> {
594    fn get_lines(&self, hl: &HighlightMap) -> LineContent {
595        let mut content_line = self.content.to_attributed_content(hl);
596        let (prompt_offset, prompt_lines) =
597            prompt_lines(&self.firstc, &self.prompt, self.indent, hl);
598
599        let mut content: Vec<_> = prompt_lines.into_iter().map(|line| vec![line]).collect();
600
601        if content.is_empty() {
602            content.push(content_line.remove(0));
603        } else {
604            if let Some(line) = content.last_mut() {
605                line.extend(content_line.remove(0))
606            }
607        }
608
609        LineContent {
610            lines: content,
611            prompt_offset,
612        }
613    }
614}
615
616struct LineContent {
617    lines: Vec<Vec<(Rc<Highlight>, Vec<String>)>>,
618    prompt_offset: usize,
619}
620
621trait ToAttributedModelContent {
622    fn to_attributed_content(&self, hl: &HighlightMap) -> Vec<Vec<(Rc<Highlight>, Vec<String>)>>;
623}
624
625impl ToAttributedModelContent for Vec<Vec<(u64, String)>> {
626    fn to_attributed_content(&self, hl: &HighlightMap) -> Vec<Vec<(Rc<Highlight>, Vec<String>)>> {
627        self.iter()
628            .map(|line_chars| {
629                line_chars
630                    .iter()
631                    .map(|c| {
632                        (
633                            hl.get(c.0.into()),
634                            c.1.graphemes(true).map(|g| g.to_owned()).collect(),
635                        )
636                    })
637                    .collect()
638            })
639            .collect()
640    }
641}
642
643impl ToAttributedModelContent for Vec<(u64, String)> {
644    fn to_attributed_content(&self, hl: &HighlightMap) -> Vec<Vec<(Rc<Highlight>, Vec<String>)>> {
645        vec![self
646            .iter()
647            .map(|c| {
648                (
649                    hl.get(c.0.into()),
650                    c.1.graphemes(true).map(|g| g.to_owned()).collect(),
651                )
652            })
653            .collect()]
654    }
655}