wip: Getting the new prompt to render in a new layer.

pull/6/head
Blaž Hrastnik 4 years ago
parent 5103dc9617
commit ada3f92c5b

@ -42,16 +42,15 @@ type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
const BASE_WIDTH: u16 = 30; const BASE_WIDTH: u16 = 30;
pub struct Application<'a> { pub struct Application<'a> {
prompt: Option<Prompt>,
compositor: Compositor, compositor: Compositor,
editor: Editor,
renderer: Renderer, renderer: Renderer,
executor: &'a smol::Executor<'a>, executor: &'a smol::Executor<'a>,
language_server: helix_lsp::Client, language_server: helix_lsp::Client,
} }
pub(crate) struct Renderer { pub struct Renderer {
size: (u16, u16), size: (u16, u16),
terminal: Terminal, terminal: Terminal,
surface: Surface, surface: Surface,
@ -263,6 +262,11 @@ impl Renderer {
self.surface self.surface
.set_string(1, self.size.1 - 2, mode, self.text_color); .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.surface.set_string(
self.size.0 - 10, self.size.0 - 10,
self.size.1 - 2, 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 // completion
if !prompt.completion.is_empty() { if !prompt.completion.is_empty() {
// TODO: find out better way of clearing individual lines of the screen // 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 { let pos = if let Some(prompt) = prompt {
Position::new(self.size.0 as usize, 2 + prompt.cursor) Position::new(self.size.0 as usize, 2 + prompt.cursor)
} else { } 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 cursor = view.doc.state.selection().cursor();
let mut pos = view let mut pos = view
@ -367,146 +362,77 @@ impl Renderer {
} }
struct EditorView { struct EditorView {
editor: Editor,
prompt: Option<Prompt>, // TODO: this is None for now, make a layer
keymap: Keymaps, keymap: Keymaps,
} }
impl EditorView { impl EditorView {
fn new(editor: Editor) -> Self { fn new() -> Self {
Self { Self {
editor,
prompt: None,
keymap: keymap::default(), keymap: keymap::default(),
} }
} }
} }
use crate::compositor::Context;
impl Component for EditorView { 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 { match event {
Event::Resize(width, height) => { Event::Resize(width, height) => {
// TODO: simplistic ensure cursor in view for now // TODO: simplistic ensure cursor in view for now
// TODO: loop over views // 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.size = (width, height);
view.ensure_cursor_in_view() view.ensure_cursor_in_view()
}; };
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(event) => { Event::Key(event) => {
if let Some(view) = self.editor.view_mut() { if let Some(view) = cx.editor.view_mut() {
let keys = vec![event]; let keys = vec![event];
// TODO: sequences (`gg`) // TODO: sequences (`gg`)
let mode = view.doc.mode();
// TODO: handle count other than 1 // 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 => { Mode::Insert => {
if let Some(command) = self.keymap[&Mode::Insert].get(&keys) { if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
let mut cx = commands::Context {
view,
executor,
count: 1,
};
command(&mut cx); command(&mut cx);
} else if let KeyEvent { } else if let KeyEvent {
code: KeyCode::Char(c), code: KeyCode::Char(c),
.. ..
} = event } = event
{ {
let mut cx = commands::Context {
view,
executor,
count: 1,
};
commands::insert::insert_char(&mut cx, c); commands::insert::insert_char(&mut cx, c);
} }
view.ensure_cursor_in_view();
} }
Mode::Normal => { Mode::Normal => {
if let &[KeyEvent { if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
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,
};
command(&mut cx); command(&mut cx);
// TODO: simplistic ensure cursor in view for now // TODO: simplistic ensure cursor in view for now
view.ensure_cursor_in_view();
} }
} }
mode => { mode => {
if let Some(command) = self.keymap[&mode].get(&keys) { if let Some(command) = self.keymap[&mode].get(&keys) {
let mut cx = commands::Context {
view,
executor,
count: 1,
};
command(&mut cx); command(&mut cx);
// TODO: simplistic ensure cursor in view for now // 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 { } else {
EventResult::Ignored EventResult::Ignored
} }
@ -514,19 +440,19 @@ impl Component for EditorView {
Event::Mouse(_) => EventResult::Ignored, 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 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 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 // 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. // theme. Theme is immutable mutating view won't disrupt theme_ref.
let theme_ref = unsafe { &*(&self.editor.theme as *const Theme) }; let theme_ref = unsafe { &*(&cx.editor.theme as *const Theme) };
if let Some(view) = self.editor.view_mut() { if let Some(view) = cx.editor.view_mut() {
renderer.render_view(view, viewport, theme_ref); renderer.render_view(view, viewport, theme_ref);
} }
// TODO: drop unwrap // 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(); 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 language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
let mut app = Self { let mut app = Self {
editor,
renderer, renderer,
// TODO; move to state // TODO; move to state
compositor, compositor,
@ -558,7 +485,11 @@ impl<'a> Application<'a> {
fn render(&mut self) { fn render(&mut self) {
self.renderer.surface.reset(); // reset is faster than allocating new empty surface 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(); self.renderer.draw_and_swap();
} }
@ -569,17 +500,16 @@ impl<'a> Application<'a> {
self.language_server.initialize().await.unwrap(); self.language_server.initialize().await.unwrap();
// TODO: temp // TODO: temp
// self.language_server // self.language_server
// .text_document_did_open(&self.editor.view().unwrap().doc) // .text_document_did_open(&cx.editor.view().unwrap().doc)
// .await // .await
// .unwrap(); // .unwrap();
self.render(); self.render();
loop { loop {
// TODO: if self.editor.should_close {
// if self.editor.should_close { break;
// break; }
// }
use futures_util::{select, FutureExt}; use futures_util::{select, FutureExt};
select! { select! {
@ -594,26 +524,26 @@ impl<'a> Application<'a> {
} }
pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) { pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
let mut cx = crate::compositor::Context {
editor: &mut self.editor,
executor: &self.executor,
};
// Handle key events // Handle key events
match event { let should_redraw = match event {
Some(Ok(Event::Resize(width, height))) => { Some(Ok(Event::Resize(width, height))) => {
self.renderer.resize(width, height); self.renderer.resize(width, height);
// TODO: use the response
self.compositor self.compositor
.handle_event(Event::Resize(width, height), self.executor); .handle_event(Event::Resize(width, height), &mut cx)
self.render();
}
Some(Ok(event)) => {
// TODO: use the response
self.compositor.handle_event(event, self.executor);
self.render();
} }
Some(Ok(event)) => self.compositor.handle_event(event, &mut cx),
Some(Err(x)) => panic!(x), Some(Err(x)) => panic!(x),
None => panic!(), None => panic!(),
}; };
if should_redraw {
self.render();
}
} }
pub async fn handle_language_server_message(&mut self, call: Option<helix_lsp::Call>) { pub async fn handle_language_server_message(&mut self, call: Option<helix_lsp::Call>) {

@ -9,17 +9,21 @@ use helix_core::{
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::compositor::Compositor;
use crate::prompt::Prompt; use crate::prompt::Prompt;
use helix_view::{ use helix_view::{
document::Mode, document::Mode,
view::{View, PADDING}, view::{View, PADDING},
Editor,
}; };
pub struct Context<'a, 'b> { pub struct Context<'a, 'b> {
pub count: usize, pub count: usize,
pub view: &'a mut View, pub view: &'a mut View,
pub executor: &'a smol::Executor<'b>, pub executor: &'a smol::Executor<'b>,
pub callback: Option<crate::compositor::Callback>,
} }
/// A command is a function that takes the current state and a count, and does a side-effect on the /// 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. // TODO: I, A, o and O can share a lot of the primitives.
pub fn command_mode(_cx: &mut Context) { pub fn command_mode(cx: &mut Context) {
unimplemented!() 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 // calculate line numbers for each selection range

@ -18,7 +18,7 @@ use crossterm::event::Event;
use smol::Executor; use smol::Executor;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
pub(crate) type Callback = Box<dyn Fn(&mut Compositor)>; pub type Callback = Box<dyn Fn(&mut Compositor)>;
// --> EventResult should have a callback that takes a context with methods like .popup(), // --> EventResult should have a callback that takes a context with methods like .popup(),
// .prompt() etc. That way we can abstract it from the renderer. // .prompt() etc. That way we can abstract it from the renderer.
@ -29,14 +29,21 @@ pub(crate) type Callback = Box<dyn Fn(&mut Compositor)>;
// If Compositor was specified in the callback that's then problematic because of // If Compositor was specified in the callback that's then problematic because of
// Cursive-inspired // Cursive-inspired
pub(crate) enum EventResult { pub enum EventResult {
Ignored, Ignored,
Consumed(Option<Callback>), Consumed(Option<Callback>),
} }
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. /// 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: () // , args: ()
/// Should redraw? Useful for saving redraw cycles if we know component didn't change. /// Should redraw? Useful for saving redraw cycles if we know component didn't change.
@ -44,7 +51,7 @@ pub(crate) trait Component {
true true
} }
fn render(&mut self, renderer: &mut Renderer); fn render(&mut self, renderer: &mut Renderer, ctx: &mut Context);
} }
// struct Editor { }; // struct Editor { };
@ -94,7 +101,7 @@ pub(crate) trait Component {
// // 2) Alternatively, // // 2) Alternatively,
//} //}
pub(crate) struct Compositor { pub struct Compositor {
layers: Vec<Box<dyn Component>>, layers: Vec<Box<dyn Component>>,
} }
@ -111,17 +118,24 @@ impl Compositor {
self.layers.pop(); 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 // TODO: custom focus
if let Some(layer) = self.layers.last_mut() { if let Some(layer) = self.layers.last_mut() {
layer.handle_event(event, executor); return match layer.handle_event(event, cx) {
// return should_update 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 { for layer in &mut self.layers {
layer.render(renderer) layer.render(renderer, cx)
} }
} }
} }

@ -1,5 +1,9 @@
use crate::Editor; use crate::{
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; application::Renderer,
compositor::{Component, Context, EventResult},
};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use helix_view::Editor;
use std::string::String; use std::string::String;
pub struct Prompt { pub struct Prompt {
@ -79,9 +83,16 @@ impl Prompt {
pub fn exit_selection(&mut self) { pub fn exit_selection(&mut self) {
self.completion_selection_index = None; 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 event {
match key_event {
KeyEvent { KeyEvent {
code: KeyCode::Char(c), code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
@ -112,7 +123,7 @@ impl Prompt {
KeyEvent { KeyEvent {
code: KeyCode::Enter, code: KeyCode::Enter,
.. ..
} => (self.callback_fn)(editor, &self.line), } => (self.callback_fn)(cx.editor, &self.line),
KeyEvent { KeyEvent {
code: KeyCode::Tab, .. code: KeyCode::Tab, ..
} => self.change_completion_selection(), } => self.change_completion_selection(),
@ -121,6 +132,12 @@ impl Prompt {
modifiers: KeyModifiers::CONTROL, modifiers: KeyModifiers::CONTROL,
} => self.exit_selection(), } => self.exit_selection(),
_ => (), _ => (),
} };
EventResult::Consumed(None)
}
fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) {
renderer.render_prompt(self, &cx.editor.theme)
} }
} }

Loading…
Cancel
Save