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 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: >k::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::<>k::Adjustment>::None,
335 Option::<>k::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(>k::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 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 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 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 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::<>k::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}