From ada3f92c5b96e4c66f5647c4ac2487f3903692b3 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 11 Dec 2020 18:25:09 +0900 Subject: wip: Getting the new prompt to render in a new layer. --- helix-term/src/application.rs | 182 +++++++++++++----------------------------- helix-term/src/commands.rs | 57 ++++++++++++- helix-term/src/compositor.rs | 36 ++++++--- helix-term/src/prompt.rs | 29 +++++-- 4 files changed, 159 insertions(+), 145 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 8c454b5d..35d698d1 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -42,16 +42,15 @@ type Terminal = tui::Terminal>; const BASE_WIDTH: u16 = 30; pub struct Application<'a> { - prompt: Option, - compositor: Compositor, + editor: Editor, renderer: Renderer, executor: &'a smol::Executor<'a>, language_server: helix_lsp::Client, } -pub(crate) struct Renderer { +pub struct Renderer { size: (u16, u16), terminal: Terminal, surface: Surface, @@ -263,6 +262,11 @@ impl Renderer { self.surface .set_string(1, self.size.1 - 2, mode, self.text_color); + if let Some(path) = view.doc.path() { + self.surface + .set_string(6, self.size.1 - 2, path.to_string_lossy(), self.text_color); + } + self.surface.set_string( self.size.0 - 10, self.size.1 - 2, @@ -271,7 +275,7 @@ impl Renderer { ); } - pub fn render_prompt(&mut self, view: &View, prompt: &Prompt, theme: &Theme) { + pub fn render_prompt(&mut self, prompt: &Prompt, theme: &Theme) { // completion if !prompt.completion.is_empty() { // TODO: find out better way of clearing individual lines of the screen @@ -343,15 +347,6 @@ impl Renderer { let pos = if let Some(prompt) = prompt { Position::new(self.size.0 as usize, 2 + prompt.cursor) } else { - if let Some(path) = view.doc.path() { - self.surface.set_string( - 6, - self.size.1 - 1, - path.to_string_lossy(), - self.text_color, - ); - } - let cursor = view.doc.state.selection().cursor(); let mut pos = view @@ -367,146 +362,77 @@ impl Renderer { } struct EditorView { - editor: Editor, - prompt: Option, // TODO: this is None for now, make a layer keymap: Keymaps, } impl EditorView { - fn new(editor: Editor) -> Self { + fn new() -> Self { Self { - editor, - prompt: None, keymap: keymap::default(), } } } +use crate::compositor::Context; + impl Component for EditorView { - fn handle_event(&mut self, event: Event, executor: &smol::Executor) -> EventResult { + fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { match event { Event::Resize(width, height) => { // TODO: simplistic ensure cursor in view for now // TODO: loop over views - if let Some(view) = self.editor.view_mut() { + if let Some(view) = cx.editor.view_mut() { view.size = (width, height); view.ensure_cursor_in_view() }; EventResult::Consumed(None) } Event::Key(event) => { - if let Some(view) = self.editor.view_mut() { + if let Some(view) = cx.editor.view_mut() { let keys = vec![event]; // TODO: sequences (`gg`) + let mode = view.doc.mode(); // TODO: handle count other than 1 - match view.doc.mode() { + let mut cx = commands::Context { + view, + executor: cx.executor, + count: 1, + callback: None, + }; + + match mode { Mode::Insert => { if let Some(command) = self.keymap[&Mode::Insert].get(&keys) { - let mut cx = commands::Context { - view, - executor, - count: 1, - }; - command(&mut cx); } else if let KeyEvent { code: KeyCode::Char(c), .. } = event { - let mut cx = commands::Context { - view, - executor, - count: 1, - }; commands::insert::insert_char(&mut cx, c); } - view.ensure_cursor_in_view(); } Mode::Normal => { - if let &[KeyEvent { - code: KeyCode::Char(':'), - .. - }] = keys.as_slice() - { - let prompt = Prompt::new( - ":".to_owned(), - |_input: &str| { - // TODO: i need this duplicate list right now to avoid borrow checker issues - let command_list = vec![ - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - String::from("q"), - String::from("aaa"), - String::from("bbb"), - String::from("ccc"), - String::from("ddd"), - String::from("eee"), - ]; - command_list - .into_iter() - .filter(|command| command.contains(_input)) - .collect() - }, // completion - |editor: &mut Editor, input: &str| match input { - "q" => editor.should_close = true, - _ => (), - }, - ); - - self.prompt = Some(prompt); - - // HAXX: special casing for command mode - } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) { - let mut cx = commands::Context { - view, - executor, - count: 1, - }; + if let Some(command) = self.keymap[&Mode::Normal].get(&keys) { command(&mut cx); // TODO: simplistic ensure cursor in view for now - view.ensure_cursor_in_view(); } } mode => { if let Some(command) = self.keymap[&mode].get(&keys) { - let mut cx = commands::Context { - view, - executor, - count: 1, - }; command(&mut cx); // TODO: simplistic ensure cursor in view for now - view.ensure_cursor_in_view(); } } } - EventResult::Consumed(None) + // appease borrowck + let callback = cx.callback.take(); + + view.ensure_cursor_in_view(); + + EventResult::Consumed(callback) } else { EventResult::Ignored } @@ -514,19 +440,19 @@ impl Component for EditorView { Event::Mouse(_) => EventResult::Ignored, } } - fn render(&mut self, renderer: &mut Renderer) { + fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) { const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter let viewport = Rect::new(OFFSET, 0, renderer.size.0, renderer.size.1 - 2); // - 2 for statusline and prompt // SAFETY: we cheat around the view_mut() borrow because it doesn't allow us to also borrow // theme. Theme is immutable mutating view won't disrupt theme_ref. - let theme_ref = unsafe { &*(&self.editor.theme as *const Theme) }; - if let Some(view) = self.editor.view_mut() { + let theme_ref = unsafe { &*(&cx.editor.theme as *const Theme) }; + if let Some(view) = cx.editor.view_mut() { renderer.render_view(view, viewport, theme_ref); } // TODO: drop unwrap - renderer.render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport); + renderer.render_cursor(cx.editor.view().unwrap(), None, viewport); } } @@ -540,11 +466,12 @@ impl<'a> Application<'a> { } let mut compositor = Compositor::new(); - compositor.push(Box::new(EditorView::new(editor))); + compositor.push(Box::new(EditorView::new())); let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]); let mut app = Self { + editor, renderer, // TODO; move to state compositor, @@ -558,7 +485,11 @@ impl<'a> Application<'a> { fn render(&mut self) { self.renderer.surface.reset(); // reset is faster than allocating new empty surface - self.compositor.render(&mut self.renderer); // viewport, + let mut cx = crate::compositor::Context { + editor: &mut self.editor, + executor: &self.executor, + }; + self.compositor.render(&mut self.renderer, &mut cx); // viewport, self.renderer.draw_and_swap(); } @@ -569,17 +500,16 @@ impl<'a> Application<'a> { self.language_server.initialize().await.unwrap(); // TODO: temp // self.language_server - // .text_document_did_open(&self.editor.view().unwrap().doc) + // .text_document_did_open(&cx.editor.view().unwrap().doc) // .await // .unwrap(); self.render(); loop { - // TODO: - // if self.editor.should_close { - // break; - // } + if self.editor.should_close { + break; + } use futures_util::{select, FutureExt}; select! { @@ -594,26 +524,26 @@ impl<'a> Application<'a> { } pub fn handle_terminal_events(&mut self, event: Option>) { + let mut cx = crate::compositor::Context { + editor: &mut self.editor, + executor: &self.executor, + }; // Handle key events - match event { + let should_redraw = match event { Some(Ok(Event::Resize(width, height))) => { self.renderer.resize(width, height); - // TODO: use the response self.compositor - .handle_event(Event::Resize(width, height), self.executor); - - self.render(); - } - Some(Ok(event)) => { - // TODO: use the response - self.compositor.handle_event(event, self.executor); - - self.render(); + .handle_event(Event::Resize(width, height), &mut cx) } + Some(Ok(event)) => self.compositor.handle_event(event, &mut cx), Some(Err(x)) => panic!(x), None => panic!(), }; + + if should_redraw { + self.render(); + } } pub async fn handle_language_server_message(&mut self, call: Option) { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a791f243..04482ef7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -9,17 +9,21 @@ use helix_core::{ use once_cell::sync::Lazy; +use crate::compositor::Compositor; use crate::prompt::Prompt; use helix_view::{ document::Mode, view::{View, PADDING}, + Editor, }; pub struct Context<'a, 'b> { pub count: usize, pub view: &'a mut View, pub executor: &'a smol::Executor<'b>, + + pub callback: Option, } /// A command is a function that takes the current state and a count, and does a side-effect on the @@ -333,8 +337,57 @@ pub fn append_mode(cx: &mut Context) { } // TODO: I, A, o and O can share a lot of the primitives. -pub fn command_mode(_cx: &mut Context) { - unimplemented!() +pub fn command_mode(cx: &mut Context) { + cx.callback = Some(Box::new(|compositor: &mut Compositor| { + let prompt = Prompt::new( + ":".to_owned(), + |_input: &str| { + // TODO: i need this duplicate list right now to avoid borrow checker issues + let command_list = vec![ + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + String::from("q"), + String::from("aaa"), + String::from("bbb"), + String::from("ccc"), + String::from("ddd"), + String::from("eee"), + ]; + command_list + .into_iter() + .filter(|command| command.contains(_input)) + .collect() + }, // completion + |editor: &mut Editor, input: &str| match input { + "q" => editor.should_close = true, + _ => (), + }, + ); + compositor.push(Box::new(prompt)); + })); } // calculate line numbers for each selection range diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 158a8b28..3cf6bf03 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -18,7 +18,7 @@ use crossterm::event::Event; use smol::Executor; use tui::buffer::Buffer as Surface; -pub(crate) type Callback = Box; +pub type Callback = Box; // --> EventResult should have a callback that takes a context with methods like .popup(), // .prompt() etc. That way we can abstract it from the renderer. @@ -29,14 +29,21 @@ pub(crate) type Callback = Box; // If Compositor was specified in the callback that's then problematic because of // Cursive-inspired -pub(crate) enum EventResult { +pub enum EventResult { Ignored, Consumed(Option), } -pub(crate) trait Component { +use helix_view::{Editor, View}; +// shared with commands.rs +pub struct Context<'a, 'b> { + pub editor: &'a mut Editor, + pub executor: &'a smol::Executor<'b>, +} + +pub trait Component { /// Process input events, return true if handled. - fn handle_event(&mut self, event: Event, executor: &Executor) -> EventResult; + fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult; // , args: () /// Should redraw? Useful for saving redraw cycles if we know component didn't change. @@ -44,7 +51,7 @@ pub(crate) trait Component { true } - fn render(&mut self, renderer: &mut Renderer); + fn render(&mut self, renderer: &mut Renderer, ctx: &mut Context); } // struct Editor { }; @@ -94,7 +101,7 @@ pub(crate) trait Component { // // 2) Alternatively, //} -pub(crate) struct Compositor { +pub struct Compositor { layers: Vec>, } @@ -111,17 +118,24 @@ impl Compositor { self.layers.pop(); } - pub fn handle_event(&mut self, event: Event, executor: &Executor) -> () { + pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool { // TODO: custom focus if let Some(layer) = self.layers.last_mut() { - layer.handle_event(event, executor); - // return should_update + return match layer.handle_event(event, cx) { + EventResult::Consumed(Some(callback)) => { + callback(self); + true + } + EventResult::Consumed(None) => true, + EventResult::Ignored => false, + }; } + false } - pub fn render(&mut self, renderer: &mut Renderer) { + pub fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) { for layer in &mut self.layers { - layer.render(renderer) + layer.render(renderer, cx) } } } diff --git a/helix-term/src/prompt.rs b/helix-term/src/prompt.rs index 4a39f2ec..689eac66 100644 --- a/helix-term/src/prompt.rs +++ b/helix-term/src/prompt.rs @@ -1,5 +1,9 @@ -use crate::Editor; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crate::{ + application::Renderer, + compositor::{Component, Context, EventResult}, +}; +use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use helix_view::Editor; use std::string::String; pub struct Prompt { @@ -79,9 +83,16 @@ impl Prompt { pub fn exit_selection(&mut self) { self.completion_selection_index = None; } +} + +impl Component for Prompt { + fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + let event = match event { + Event::Key(event) => event, + _ => return EventResult::Ignored, + }; - pub fn handle_event(&mut self, key_event: KeyEvent, editor: &mut Editor) { - match key_event { + match event { KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE, @@ -112,7 +123,7 @@ impl Prompt { KeyEvent { code: KeyCode::Enter, .. - } => (self.callback_fn)(editor, &self.line), + } => (self.callback_fn)(cx.editor, &self.line), KeyEvent { code: KeyCode::Tab, .. } => self.change_completion_selection(), @@ -121,6 +132,12 @@ impl Prompt { modifiers: KeyModifiers::CONTROL, } => self.exit_selection(), _ => (), - } + }; + + EventResult::Consumed(None) + } + + fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) { + renderer.render_prompt(self, &cx.editor.theme) } } -- cgit v1.2.3-70-g09d2