From 83f2c24115cc5a3dce90a77440f1ef06f6cf9c78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sun, 6 Dec 2020 11:53:58 +0900
Subject: [PATCH 1/9] wip: Compositor

---
 helix-term/src/application.rs | 256 ++++++++++++++++++++--------------
 helix-term/src/compositor.rs  | 111 +++++++++++++++
 helix-term/src/main.rs        |   1 +
 3 files changed, 261 insertions(+), 107 deletions(-)
 create mode 100644 helix-term/src/compositor.rs

diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 141779ec..30258c1d 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -8,6 +8,8 @@ use helix_view::{
     Document, Editor, Theme, View,
 };
 
+use crate::compositor::{Component, Compositor};
+
 use log::{debug, info};
 
 use std::{
@@ -35,23 +37,21 @@ use tui::{
     style::{Color, Modifier, Style},
 };
 
-const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
-
 type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
 
 const BASE_WIDTH: u16 = 30;
 
 pub struct Application<'a> {
-    editor: Editor,
     prompt: Option<Prompt>,
-    terminal: Renderer,
 
-    keymap: Keymaps,
+    compositor: Compositor,
+    renderer: Renderer,
+
     executor: &'a smol::Executor<'a>,
     language_server: helix_lsp::Client,
 }
 
-struct Renderer {
+pub(crate) struct Renderer {
     size: (u16, u16),
     terminal: Terminal,
     surface: Surface,
@@ -92,7 +92,6 @@ impl Renderer {
     // TODO: ideally not &mut View but highlights require it because of cursor cache
     pub fn render_buffer(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
         let area = Rect::new(0, 0, self.size.0, self.size.1);
-        self.surface.reset(); // reset is faster than allocating new empty surface
 
         //  clear with background color
         self.surface.set_style(area, theme.get("ui.background"));
@@ -221,8 +220,12 @@ impl Renderer {
 
                             // TODO: paint cursor heads except primary
 
-                            self.surface
-                                .set_string(OFFSET + visual_x, line, grapheme, style);
+                            self.surface.set_string(
+                                viewport.x + visual_x,
+                                viewport.y + line,
+                                grapheme,
+                                style,
+                            );
 
                             visual_x += width;
                         }
@@ -321,7 +324,7 @@ impl Renderer {
             .set_string(2, self.size.1 - 1, &prompt.line, self.text_color);
     }
 
-    pub fn draw(&mut self) {
+    pub fn draw_and_swap(&mut self) {
         use tui::backend::Backend;
         // TODO: theres probably a better place for this
         self.terminal
@@ -363,112 +366,40 @@ impl Renderer {
     }
 }
 
-impl<'a> Application<'a> {
-    pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
-        let terminal = Renderer::new()?;
-        let mut editor = Editor::new();
+struct EditorView {
+    editor: Editor,
+    prompt: Option<Prompt>, // TODO: this is None for now, make a layer
+    keymap: Keymaps,
+}
 
-        if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
-            editor.open(file, terminal.size)?;
-        }
-
-        let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
-
-        let mut app = Self {
+impl EditorView {
+    fn new(editor: Editor) -> Self {
+        Self {
             editor,
-            terminal,
-            // TODO; move to state
             prompt: None,
-
-            //
             keymap: keymap::default(),
-            executor,
-            language_server,
-        };
-
-        Ok(app)
-    }
-
-    fn render(&mut self) {
-        let viewport = Rect::new(OFFSET, 0, self.terminal.size.0, self.terminal.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() {
-            self.terminal.render_view(view, viewport, theme_ref);
-            if let Some(prompt) = &self.prompt {
-                if prompt.should_close {
-                    self.prompt = None;
-                } else {
-                    self.terminal.render_prompt(view, prompt, theme_ref);
-                }
-            }
-        }
-
-        self.terminal.draw();
-
-        // TODO: drop unwrap
-        self.terminal
-            .render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport);
-    }
-
-    pub async fn event_loop(&mut self) {
-        let mut reader = EventStream::new();
-
-        // initialize lsp
-        self.language_server.initialize().await.unwrap();
-        self.language_server
-            .text_document_did_open(&self.editor.view().unwrap().doc)
-            .await
-            .unwrap();
-
-        self.render();
-
-        loop {
-            if self.editor.should_close {
-                break;
-            }
-
-            use futures_util::{select, FutureExt};
-            select! {
-                event = reader.next().fuse() => {
-                    self.handle_terminal_events(event).await
-                }
-                call = self.language_server.incoming.next().fuse() => {
-                    self.handle_language_server_message(call).await
-                }
-            }
         }
     }
+}
 
-    pub async fn handle_terminal_events(
-        &mut self,
-        event: Option<Result<Event, crossterm::ErrorKind>>,
-    ) {
-        // Handle key events
+impl Component for EditorView {
+    fn handle_event(&mut self, event: Event, executor: &smol::Executor) -> bool {
         match event {
-            Some(Ok(Event::Resize(width, height))) => {
-                self.terminal.resize(width, height);
-
+            Event::Resize(width, height) => {
                 // TODO: simplistic ensure cursor in view for now
                 // TODO: loop over views
                 if let Some(view) = self.editor.view_mut() {
-                    view.size = self.terminal.size;
+                    view.size = (width, height);
                     view.ensure_cursor_in_view()
                 };
-
-                self.render();
             }
-            Some(Ok(Event::Key(event))) => {
+            Event::Key(event) => {
                 // if there's a prompt, it takes priority
                 if let Some(prompt) = &mut self.prompt {
                     self.prompt
                         .as_mut()
                         .unwrap()
                         .handle_input(event, &mut self.editor);
-
-                    self.render();
                 } else if let Some(view) = self.editor.view_mut() {
                     let keys = vec![event];
                     // TODO: sequences (`gg`)
@@ -478,7 +409,7 @@ impl<'a> Application<'a> {
                             if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
                                 let mut cx = helix_view::commands::Context {
                                     view,
-                                    executor: self.executor,
+                                    executor: executor,
                                     count: 1,
                                 };
 
@@ -490,7 +421,7 @@ impl<'a> Application<'a> {
                             {
                                 let mut cx = helix_view::commands::Context {
                                     view,
-                                    executor: self.executor,
+                                    executor: executor,
                                     count: 1,
                                 };
                                 commands::insert::insert_char(&mut cx, c);
@@ -557,7 +488,7 @@ impl<'a> Application<'a> {
                             } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
                                 let mut cx = helix_view::commands::Context {
                                     view,
-                                    executor: self.executor,
+                                    executor: executor,
                                     count: 1,
                                 };
                                 command(&mut cx);
@@ -570,7 +501,7 @@ impl<'a> Application<'a> {
                             if let Some(command) = self.keymap[&mode].get(&keys) {
                                 let mut cx = helix_view::commands::Context {
                                     view,
-                                    executor: self.executor,
+                                    executor: executor,
                                     count: 1,
                                 };
                                 command(&mut cx);
@@ -580,10 +511,119 @@ impl<'a> Application<'a> {
                             }
                         }
                     }
-                    self.render();
                 }
             }
-            Some(Ok(Event::Mouse(_))) => (), // unhandled
+            Event::Mouse(_) => (),
+        }
+
+        true
+    }
+    fn render(&mut self, renderer: &mut Renderer) {
+        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() {
+            renderer.render_view(view, viewport, theme_ref);
+            if let Some(prompt) = &self.prompt {
+                if prompt.should_close {
+                    self.prompt = None;
+                } else {
+                    renderer.render_prompt(view, prompt, theme_ref);
+                }
+            }
+        }
+
+        // TODO: drop unwrap
+        renderer.render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport);
+    }
+}
+
+impl<'a> Application<'a> {
+    pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
+        let renderer = Renderer::new()?;
+        let mut editor = Editor::new();
+
+        if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
+            editor.open(file, renderer.size)?;
+        }
+
+        let mut compositor = Compositor::new();
+        compositor.push(Box::new(EditorView::new(editor)));
+
+        let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
+
+        let mut app = Self {
+            renderer,
+            // TODO; move to state
+            compositor,
+            prompt: None,
+
+            executor,
+            language_server,
+        };
+
+        Ok(app)
+    }
+
+    fn render(&mut self) {
+        // v2:
+        self.renderer.surface.reset(); // reset is faster than allocating new empty surface
+        self.compositor.render(&mut self.renderer); // viewport,
+        self.renderer.draw_and_swap();
+    }
+
+    pub async fn event_loop(&mut self) {
+        let mut reader = EventStream::new();
+
+        // initialize lsp
+        self.language_server.initialize().await.unwrap();
+        // TODO: temp
+        // self.language_server
+        //     .text_document_did_open(&self.editor.view().unwrap().doc)
+        //     .await
+        //     .unwrap();
+
+        self.render();
+
+        loop {
+            // TODO:
+            // if self.editor.should_close {
+            //     break;
+            // }
+
+            use futures_util::{select, FutureExt};
+            select! {
+                event = reader.next().fuse() => {
+                    self.handle_terminal_events(event)
+                }
+                call = self.language_server.incoming.next().fuse() => {
+                    self.handle_language_server_message(call).await
+                }
+            }
+        }
+    }
+
+    pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
+        // Handle key events
+        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();
+            }
             Some(Err(x)) => panic!(x),
             None => panic!(),
         };
@@ -599,11 +639,13 @@ impl<'a> Application<'a> {
                 match notification {
                     Notification::PublishDiagnostics(params) => {
                         let path = Some(params.uri.to_file_path().unwrap());
-                        let view = self
-                            .editor
-                            .views
-                            .iter_mut()
-                            .find(|view| view.doc.path == path);
+                        let view: Option<&mut helix_view::View> = None;
+                        // TODO:
+                        // let view = self
+                        //     .editor
+                        //     .views
+                        //     .iter_mut()
+                        //     .find(|view| view.doc.path == path);
 
                         if let Some(view) = view {
                             let doc = view.doc.text().slice(..);
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
new file mode 100644
index 00000000..187c5692
--- /dev/null
+++ b/helix-term/src/compositor.rs
@@ -0,0 +1,111 @@
+// Features:
+// Tracks currently focused component which receives all input
+// Event loop is external as opposed to cursive-rs
+// Calls render on the component and translates screen coords to local component coords
+//
+// TODO:
+// Q: where is the Application state stored? do we store it into an external static var?
+// A: probably makes sense to initialize the editor into a `static Lazy<>` global var.
+//
+// Q: how do we composit nested structures? There should be sub-components/views
+//
+// Each component declares it's own size constraints and gets fitted based on it's parent.
+// Q: how does this work with popups?
+// cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>)
+
+use crate::application::Renderer;
+use crossterm::event::Event;
+use smol::Executor;
+use tui::buffer::Buffer as Surface;
+
+pub(crate) trait Component {
+    /// Process input events, return true if handled.
+    fn handle_event(&mut self, event: Event, executor: &Executor) -> bool;
+    // , args: ()
+
+    /// Should redraw? Useful for saving redraw cycles if we know component didn't change.
+    fn should_update(&self) -> bool {
+        true
+    }
+
+    fn render(&mut self, renderer: &mut Renderer);
+}
+
+// struct Editor { };
+
+// For v1:
+// Child views are something each view needs to handle on it's own for now, positioning and sizing
+// options, focus tracking. In practice this is simple: we only will need special solving for
+// splits etc
+
+// impl Editor {
+//     fn render(&mut self, surface: &mut Surface, args: ()) {
+//         // compute x, y, w, h rects for sub-views!
+//         // get surface area
+//         // get constraints for textarea, statusbar
+//         // -> cassowary-rs
+
+//         // first render textarea
+//         // then render statusbar
+//     }
+// }
+
+// usecases to consider:
+// - a single view with subviews (textarea + statusbar)
+// - a popup panel / dialog with it's own interactions
+// - an autocomplete popup that doesn't change focus
+
+//fn main() {
+//    let root = Editor::new();
+//    let compositor = Compositor::new();
+
+//    compositor.push(root);
+
+//    // pos: clip to bottom of screen
+//    compositor.push_at(pos, Prompt::new(
+//        ":",
+//        (),
+//        |input: &str| match input {}
+//    )); // TODO: this Prompt needs to somehow call compositor.pop() on close, but it can't refer to parent
+//    // Cursive solves this by allowing to return a special result on process_event
+//    // that's either Ignore | Consumed(Opt<C>) where C: fn (Compositor) -> ()
+
+//    // TODO: solve popup focus: we want to push autocomplete popups on top of the current layer
+//    // but retain the focus where it was. The popup will also need to update as we type into the
+//    // textarea. It should also capture certain input, such as tab presses etc
+//    //
+//    // 1) This could be faked by the top layer pushing down edits into the previous layer.
+//    // 2) Alternatively,
+//}
+
+pub(crate) struct Compositor {
+    layers: Vec<Box<dyn Component>>,
+}
+
+impl Compositor {
+    pub fn new() -> Self {
+        Self { layers: Vec::new() }
+    }
+
+    pub fn push(&mut self, layer: Box<dyn Component>) {
+        self.layers.push(layer);
+    }
+
+    pub fn pop(&mut self) {
+        self.layers.pop();
+    }
+
+    pub fn handle_event(&mut self, event: Event, executor: &Executor) -> () {
+        // TODO: custom focus
+        if let Some(layer) = self.layers.last_mut() {
+            layer.handle_event(event, executor);
+            // return should_update
+        }
+    }
+
+    pub fn render(&mut self, renderer: &mut Renderer) {
+        for layer in &mut self.layers {
+            layer.render(renderer)
+        }
+    }
+}
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index 9378d3ee..a43aebd8 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -1,6 +1,7 @@
 #![allow(unused)]
 
 mod application;
+mod compositor;
 
 use application::Application;
 

From be3c02104600e5bba8e3af7bc9787e62cba30183 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Mon, 7 Dec 2020 15:18:37 +0900
Subject: [PATCH 2/9] snapshot

---
 helix-term/src/application.rs | 14 ++++++++------
 helix-term/src/compositor.rs  | 10 +++++++++-
 2 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 30258c1d..506735e8 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -8,7 +8,7 @@ use helix_view::{
     Document, Editor, Theme, View,
 };
 
-use crate::compositor::{Component, Compositor};
+use crate::compositor::{Component, Compositor, EventResult};
 
 use log::{debug, info};
 
@@ -383,7 +383,7 @@ impl EditorView {
 }
 
 impl Component for EditorView {
-    fn handle_event(&mut self, event: Event, executor: &smol::Executor) -> bool {
+    fn handle_event(&mut self, event: Event, executor: &smol::Executor) -> EventResult {
         match event {
             Event::Resize(width, height) => {
                 // TODO: simplistic ensure cursor in view for now
@@ -392,6 +392,7 @@ impl Component for EditorView {
                     view.size = (width, height);
                     view.ensure_cursor_in_view()
                 };
+                EventResult::Consumed(None)
             }
             Event::Key(event) => {
                 // if there's a prompt, it takes priority
@@ -400,6 +401,7 @@ impl Component for EditorView {
                         .as_mut()
                         .unwrap()
                         .handle_input(event, &mut self.editor);
+                    EventResult::Consumed(None)
                 } else if let Some(view) = self.editor.view_mut() {
                     let keys = vec![event];
                     // TODO: sequences (`gg`)
@@ -511,12 +513,13 @@ impl Component for EditorView {
                             }
                         }
                     }
+                    EventResult::Consumed(None)
+                } else {
+                    EventResult::Ignored
                 }
             }
-            Event::Mouse(_) => (),
+            Event::Mouse(_) => EventResult::Ignored,
         }
-
-        true
     }
     fn render(&mut self, renderer: &mut Renderer) {
         const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
@@ -569,7 +572,6 @@ impl<'a> Application<'a> {
     }
 
     fn render(&mut self) {
-        // v2:
         self.renderer.surface.reset(); // reset is faster than allocating new empty surface
         self.compositor.render(&mut self.renderer); // viewport,
         self.renderer.draw_and_swap();
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index 187c5692..f859f947 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -18,9 +18,17 @@ use crossterm::event::Event;
 use smol::Executor;
 use tui::buffer::Buffer as Surface;
 
+pub(crate) type Callback = Box<dyn Fn(&mut Compositor)>;
+
+// Cursive-inspired
+pub(crate) enum EventResult {
+    Ignored,
+    Consumed(Option<Callback>),
+}
+
 pub(crate) trait Component {
     /// Process input events, return true if handled.
-    fn handle_event(&mut self, event: Event, executor: &Executor) -> bool;
+    fn handle_event(&mut self, event: Event, executor: &Executor) -> EventResult;
     // , args: ()
 
     /// Should redraw? Useful for saving redraw cycles if we know component didn't change.

From 5103dc96173afaa1c0793db56f60ec1fef1e0fc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Thu, 10 Dec 2020 18:13:42 +0900
Subject: [PATCH 3/9] move commands and keymap back to terminal.

Command needs to be able to deal with UI. We'll separate it again later
on.
---
 Cargo.lock                                 |  1 +
 helix-term/Cargo.toml                      |  1 +
 helix-term/src/application.rs              | 47 ++++++++--------------
 {helix-view => helix-term}/src/commands.rs |  6 ++-
 helix-term/src/compositor.rs               |  8 ++++
 {helix-view => helix-term}/src/keymap.rs   |  4 +-
 helix-term/src/main.rs                     |  3 ++
 {helix-view => helix-term}/src/prompt.rs   |  2 +-
 helix-view/src/lib.rs                      |  3 --
 9 files changed, 36 insertions(+), 39 deletions(-)
 rename {helix-view => helix-term}/src/commands.rs (99%)
 rename {helix-view => helix-term}/src/keymap.rs (99%)
 rename {helix-view => helix-term}/src/prompt.rs (98%)

diff --git a/Cargo.lock b/Cargo.lock
index a3e93bd7..331934f8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -522,6 +522,7 @@ dependencies = [
  "helix-view",
  "log",
  "num_cpus",
+ "once_cell",
  "smol",
  "tui",
 ]
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index c1560ee7..b8eea7c2 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -17,6 +17,7 @@ helix-view = { path = "../helix-view", features = ["term"]}
 helix-lsp = { path = "../helix-lsp"}
 
 anyhow = "1"
+once_cell = "1.4"
 
 smol = "1"
 num_cpus = "1"
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 506735e8..8c454b5d 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -1,14 +1,14 @@
+use crate::{
+    commands,
+    keymap::{self, Keymaps},
+};
 use clap::ArgMatches as Args;
 use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
-use helix_view::{
-    commands,
-    document::Mode,
-    keymap::{self, Keymaps},
-    prompt::Prompt,
-    Document, Editor, Theme, View,
-};
+
+use helix_view::{document::Mode, Document, Editor, Theme, View};
 
 use crate::compositor::{Component, Compositor, EventResult};
+use crate::prompt::Prompt;
 
 use log::{debug, info};
 
@@ -395,23 +395,16 @@ impl Component for EditorView {
                 EventResult::Consumed(None)
             }
             Event::Key(event) => {
-                // if there's a prompt, it takes priority
-                if let Some(prompt) = &mut self.prompt {
-                    self.prompt
-                        .as_mut()
-                        .unwrap()
-                        .handle_input(event, &mut self.editor);
-                    EventResult::Consumed(None)
-                } else if let Some(view) = self.editor.view_mut() {
+                if let Some(view) = self.editor.view_mut() {
                     let keys = vec![event];
                     // TODO: sequences (`gg`)
                     // TODO: handle count other than 1
                     match view.doc.mode() {
                         Mode::Insert => {
                             if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
-                                let mut cx = helix_view::commands::Context {
+                                let mut cx = commands::Context {
                                     view,
-                                    executor: executor,
+                                    executor,
                                     count: 1,
                                 };
 
@@ -421,9 +414,9 @@ impl Component for EditorView {
                                 ..
                             } = event
                             {
-                                let mut cx = helix_view::commands::Context {
+                                let mut cx = commands::Context {
                                     view,
-                                    executor: executor,
+                                    executor,
                                     count: 1,
                                 };
                                 commands::insert::insert_char(&mut cx, c);
@@ -488,9 +481,9 @@ impl Component for EditorView {
 
                             // HAXX: special casing for command mode
                             } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
-                                let mut cx = helix_view::commands::Context {
+                                let mut cx = commands::Context {
                                     view,
-                                    executor: executor,
+                                    executor,
                                     count: 1,
                                 };
                                 command(&mut cx);
@@ -501,9 +494,9 @@ impl Component for EditorView {
                         }
                         mode => {
                             if let Some(command) = self.keymap[&mode].get(&keys) {
-                                let mut cx = helix_view::commands::Context {
+                                let mut cx = commands::Context {
                                     view,
-                                    executor: executor,
+                                    executor,
                                     count: 1,
                                 };
                                 command(&mut cx);
@@ -530,13 +523,6 @@ impl Component for EditorView {
         let theme_ref = unsafe { &*(&self.editor.theme as *const Theme) };
         if let Some(view) = self.editor.view_mut() {
             renderer.render_view(view, viewport, theme_ref);
-            if let Some(prompt) = &self.prompt {
-                if prompt.should_close {
-                    self.prompt = None;
-                } else {
-                    renderer.render_prompt(view, prompt, theme_ref);
-                }
-            }
         }
 
         // TODO: drop unwrap
@@ -562,7 +548,6 @@ impl<'a> Application<'a> {
             renderer,
             // TODO; move to state
             compositor,
-            prompt: None,
 
             executor,
             language_server,
diff --git a/helix-view/src/commands.rs b/helix-term/src/commands.rs
similarity index 99%
rename from helix-view/src/commands.rs
rename to helix-term/src/commands.rs
index c135a3da..a791f243 100644
--- a/helix-view/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -6,11 +6,13 @@ use helix_core::{
     state::{Direction, Granularity, State},
     ChangeSet, Range, Selection, Tendril, Transaction,
 };
+
 use once_cell::sync::Lazy;
 
-use crate::{
+use crate::prompt::Prompt;
+
+use helix_view::{
     document::Mode,
-    prompt::Prompt,
     view::{View, PADDING},
 };
 
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index f859f947..158a8b28 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -20,6 +20,14 @@ use tui::buffer::Buffer as Surface;
 
 pub(crate) type Callback = Box<dyn Fn(&mut Compositor)>;
 
+// --> EventResult should have a callback that takes a context with methods like .popup(),
+// .prompt() etc. That way we can abstract it from the renderer.
+// Q: How does this interact with popups where we need to be able to specify the rendering of the
+// popup?
+// A: It could just take a textarea.
+//
+// If Compositor was specified in the callback that's then problematic because of
+
 // Cursive-inspired
 pub(crate) enum EventResult {
     Ignored,
diff --git a/helix-view/src/keymap.rs b/helix-term/src/keymap.rs
similarity index 99%
rename from helix-view/src/keymap.rs
rename to helix-term/src/keymap.rs
index c815911e..af46f7a4 100644
--- a/helix-view/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -1,6 +1,6 @@
 use crate::commands::{self, Command};
-use crate::document::Mode;
 use helix_core::hashmap;
+use helix_view::document::Mode;
 use std::collections::HashMap;
 
 // Kakoune-inspired:
@@ -87,7 +87,7 @@ use std::collections::HashMap;
 //      gr = goto reference
 // }
 
-#[cfg(feature = "term")]
+// #[cfg(feature = "term")]
 pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
 
 // TODO: could be trie based
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index a43aebd8..92ab10c2 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -1,7 +1,10 @@
 #![allow(unused)]
 
 mod application;
+mod commands;
 mod compositor;
+mod keymap;
+mod prompt;
 
 use application::Application;
 
diff --git a/helix-view/src/prompt.rs b/helix-term/src/prompt.rs
similarity index 98%
rename from helix-view/src/prompt.rs
rename to helix-term/src/prompt.rs
index e2a9c80d..4a39f2ec 100644
--- a/helix-view/src/prompt.rs
+++ b/helix-term/src/prompt.rs
@@ -80,7 +80,7 @@ impl Prompt {
         self.completion_selection_index = None;
     }
 
-    pub fn handle_input(&mut self, key_event: KeyEvent, editor: &mut Editor) {
+    pub fn handle_event(&mut self, key_event: KeyEvent, editor: &mut Editor) {
         match key_event {
             KeyEvent {
                 code: KeyCode::Char(c),
diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs
index 3b923744..f28c8116 100644
--- a/helix-view/src/lib.rs
+++ b/helix-view/src/lib.rs
@@ -1,8 +1,5 @@
-pub mod commands;
 pub mod document;
 pub mod editor;
-pub mod keymap;
-pub mod prompt;
 pub mod theme;
 pub mod view;
 

From ada3f92c5b96e4c66f5647c4ac2487f3903692b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Fri, 11 Dec 2020 18:25:09 +0900
Subject: [PATCH 4/9] 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<CrosstermBackend<std::io::Stdout>>;
 const BASE_WIDTH: u16 = 30;
 
 pub struct Application<'a> {
-    prompt: Option<Prompt>,
-
     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<Prompt>, // 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<Result<Event, crossterm::ErrorKind>>) {
+        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<helix_lsp::Call>) {
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<crate::compositor::Callback>,
 }
 
 /// 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<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(),
 // .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
 
 // Cursive-inspired
-pub(crate) enum EventResult {
+pub enum EventResult {
     Ignored,
     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.
-    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<Box<dyn Component>>,
 }
 
@@ -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;
     }
+}
 
-    pub fn handle_event(&mut self, key_event: KeyEvent, editor: &mut Editor) {
-        match key_event {
+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,
+        };
+
+        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)
     }
 }

From 29cb33300b1486c778e9318e87e60c26695c2520 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sat, 12 Dec 2020 20:18:44 +0900
Subject: [PATCH 5/9] wip

---
 helix-term/src/application.rs | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 35d698d1..589aaf6e 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -336,6 +336,7 @@ impl Renderer {
             .draw(self.cache.diff(&self.surface).into_iter());
         // swap the buffer
         std::mem::swap(&mut self.surface, &mut self.cache);
+        self.surface.reset(); // reset is faster than allocating new empty surface
     }
 
     pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
@@ -412,13 +413,6 @@ impl Component for EditorView {
                                 commands::insert::insert_char(&mut cx, c);
                             }
                         }
-                        Mode::Normal => {
-                            if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
-                                command(&mut cx);
-
-                                // TODO: simplistic ensure cursor in view for now
-                            }
-                        }
                         mode => {
                             if let Some(command) = self.keymap[&mode].get(&keys) {
                                 command(&mut cx);
@@ -484,7 +478,6 @@ impl<'a> Application<'a> {
     }
 
     fn render(&mut self) {
-        self.renderer.surface.reset(); // reset is faster than allocating new empty surface
         let mut cx = crate::compositor::Context {
             editor: &mut self.editor,
             executor: &self.executor,
@@ -543,6 +536,7 @@ impl<'a> Application<'a> {
 
         if should_redraw {
             self.render();
+            // calling render twice here fixes it for some reason
         }
     }
 

From 8695415fbfe927250f68e93793660e3c4e4a70b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sun, 13 Dec 2020 12:23:50 +0900
Subject: [PATCH 6/9] wip: Move to new rendering structure.

---
 Cargo.lock                    |  50 ++--
 helix-lsp/src/transport.rs    |   1 -
 helix-syntax/languages.toml   |   5 +
 helix-term/src/application.rs | 467 ++++------------------------------
 helix-term/src/compositor.rs  |  14 +-
 helix-term/src/editor_view.rs | 311 ++++++++++++++++++++++
 helix-term/src/main.rs        |   2 +
 helix-term/src/prompt.rs      |  72 +++++-
 helix-term/src/terminal.rs    | 221 ++++++++++++++++
 9 files changed, 680 insertions(+), 463 deletions(-)
 create mode 100644 helix-syntax/languages.toml
 create mode 100644 helix-term/src/editor_view.rs
 create mode 100644 helix-term/src/terminal.rs

diff --git a/Cargo.lock b/Cargo.lock
index 331934f8..1c8c86c7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,9 +11,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.34"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
+checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
 
 [[package]]
 name = "arrayref"
@@ -195,9 +195,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
 
 [[package]]
 name = "cc"
-version = "1.0.65"
+version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
 dependencies = [
  "jobserver",
 ]
@@ -242,15 +242,6 @@ dependencies = [
  "vec_map",
 ]
 
-[[package]]
-name = "cloudabi"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
-dependencies = [
- "bitflags",
-]
-
 [[package]]
 name = "concurrent-queue"
 version = "1.2.2"
@@ -615,9 +606,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
 
 [[package]]
 name = "lock_api"
@@ -777,12 +768,11 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
+checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0"
 dependencies = [
- "cfg-if 0.1.10",
- "cloudabi",
+ "cfg-if 1.0.0",
  "instant",
  "libc",
  "redox_syscall",
@@ -947,18 +937,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
 name = "serde"
-version = "1.0.117"
+version = "1.0.118"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
+checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.117"
+version = "1.0.118"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
+checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -967,9 +957,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.59"
+version = "1.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
+checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
 dependencies = [
  "itoa",
  "ryu",
@@ -1024,9 +1014,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 
 [[package]]
 name = "smallvec"
-version = "1.5.0"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
+checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
 
 [[package]]
 name = "smol"
@@ -1060,9 +1050,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.53"
+version = "1.0.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
+checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1156,7 +1146,7 @@ dependencies = [
 [[package]]
 name = "tui"
 version = "0.13.0"
-source = "git+https://github.com/fdehau/tui-rs#efdd6bfb193dafcb5e3bdc75e7d2d314065da1d7"
+source = "git+https://github.com/fdehau/tui-rs#74243394d90ea1316b6bedac6c9e4f26971c76b6"
 dependencies = [
  "bitflags",
  "cassowary",
diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs
index 4c349a13..22af1b40 100644
--- a/helix-lsp/src/transport.rs
+++ b/helix-lsp/src/transport.rs
@@ -177,7 +177,6 @@ impl Transport {
                     .expect("pending_request with id not found!");
                 tx.send(Err(error.into())).await?;
             }
-            msg => unimplemented!("{:?}", msg),
         }
         Ok(())
     }
diff --git a/helix-syntax/languages.toml b/helix-syntax/languages.toml
new file mode 100644
index 00000000..dc4fcf6f
--- /dev/null
+++ b/helix-syntax/languages.toml
@@ -0,0 +1,5 @@
+[[language]]
+name = "rust"
+scope = "source.rust"
+injection-regex = "rust"
+file-types = ["rs"]
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 589aaf6e..7a74f8ba 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -1,13 +1,9 @@
-use crate::{
-    commands,
-    keymap::{self, Keymaps},
-};
 use clap::ArgMatches as Args;
-use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
 
 use helix_view::{document::Mode, Document, Editor, Theme, View};
 
 use crate::compositor::{Component, Compositor, EventResult};
+use crate::editor_view::EditorView;
 use crate::prompt::Prompt;
 
 use log::{debug, info};
@@ -37,426 +33,54 @@ use tui::{
     style::{Color, Modifier, Style},
 };
 
-type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
+type Terminal = crate::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
 
-const BASE_WIDTH: u16 = 30;
-
-pub struct Application<'a> {
+pub struct Application {
     compositor: Compositor,
     editor: Editor,
-    renderer: Renderer,
+    terminal: Terminal,
 
-    executor: &'a smol::Executor<'a>,
+    executor: &'static smol::Executor<'static>,
     language_server: helix_lsp::Client,
 }
 
-pub struct Renderer {
-    size: (u16, u16),
-    terminal: Terminal,
-    surface: Surface,
-    cache: Surface,
-    text_color: Style,
+// TODO: temp
+#[inline(always)]
+pub fn text_color() -> Style {
+    return Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
 }
 
-impl Renderer {
-    pub fn new() -> Result<Self, Error> {
+// pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
+//     let mut stdout = stdout();
+//     match view.doc.mode() {
+//         Mode::Insert => write!(stdout, "\x1B[6 q"),
+//         mode => write!(stdout, "\x1B[2 q"),
+//     };
+//     let pos = if let Some(prompt) = prompt {
+//         Position::new(self.size.0 as usize, 2 + prompt.cursor)
+//     } else {
+//         let cursor = view.doc.state.selection().cursor();
+
+//         let mut pos = view
+//             .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
+//             .expect("Cursor is out of bounds.");
+//         pos.col += viewport.x as usize;
+//         pos.row += viewport.y as usize;
+//         pos
+//     };
+
+//     execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
+// }
+
+impl Application {
+    pub fn new(mut args: Args, executor: &'static smol::Executor<'static>) -> Result<Self, Error> {
         let backend = CrosstermBackend::new(stdout());
         let mut terminal = Terminal::new(backend)?;
-        let size = terminal::size().unwrap();
-        let text_color: Style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
-
-        let area = Rect::new(0, 0, size.0, size.1);
-
-        Ok(Self {
-            size,
-            terminal,
-            surface: Surface::empty(area),
-            cache: Surface::empty(area),
-            text_color,
-        })
-    }
-
-    pub fn resize(&mut self, width: u16, height: u16) {
-        self.size = (width, height);
-        let area = Rect::new(0, 0, width, height);
-        self.surface = Surface::empty(area);
-        self.cache = Surface::empty(area);
-    }
-
-    pub fn render_view(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
-        self.render_buffer(view, viewport, theme);
-        self.render_statusline(view, theme);
-    }
-
-    // TODO: ideally not &mut View but highlights require it because of cursor cache
-    pub fn render_buffer(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
-        let area = Rect::new(0, 0, self.size.0, self.size.1);
-
-        //  clear with background color
-        self.surface.set_style(area, theme.get("ui.background"));
-
-        // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
-        let source_code = view.doc.text().to_string();
-
-        let last_line = view.last_line();
-
-        let range = {
-            // calculate viewport byte ranges
-            let start = view.doc.text().line_to_byte(view.first_line);
-            let end = view.doc.text().line_to_byte(last_line)
-                + view.doc.text().line(last_line).len_bytes();
-
-            start..end
-        };
-
-        // TODO: range doesn't actually restrict source, just highlight range
-        // TODO: cache highlight results
-        // TODO: only recalculate when state.doc is actually modified
-        let highlights: Vec<_> = match view.doc.syntax.as_mut() {
-            Some(syntax) => {
-                syntax
-                    .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
-                    .unwrap()
-                    .collect() // TODO: we collect here to avoid double borrow, fix later
-            }
-            None => vec![Ok(HighlightEvent::Source {
-                start: range.start,
-                end: range.end,
-            })],
-        };
-        let mut spans = Vec::new();
-        let mut visual_x = 0;
-        let mut line = 0u16;
-        let visible_selections: Vec<Range> = view
-            .doc
-            .state
-            .selection()
-            .ranges()
-            .iter()
-            // TODO: limit selection to one in viewport
-            // .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1))
-            .copied()
-            .collect();
-
-        'outer: for event in highlights {
-            match event.unwrap() {
-                HighlightEvent::HighlightStart(span) => {
-                    spans.push(span);
-                }
-                HighlightEvent::HighlightEnd => {
-                    spans.pop();
-                }
-                HighlightEvent::Source { start, end } => {
-                    // TODO: filter out spans out of viewport for now..
-
-                    let start = view.doc.text().byte_to_char(start);
-                    let end = view.doc.text().byte_to_char(end); // <-- index 744, len 743
-
-                    let text = view.doc.text().slice(start..end);
-
-                    use helix_core::graphemes::{grapheme_width, RopeGraphemes};
-
-                    let style = match spans.first() {
-                        Some(span) => theme.get(theme.scopes()[span.0].as_str()),
-                        None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
-                    };
-
-                    // TODO: we could render the text to a surface, then cache that, that
-                    // way if only the selection/cursor changes we can copy from cache
-                    // and paint the new cursor.
-
-                    let mut char_index = start;
-
-                    // iterate over range char by char
-                    for grapheme in RopeGraphemes::new(&text) {
-                        // TODO: track current char_index
-
-                        if grapheme == "\n" {
-                            visual_x = 0;
-                            line += 1;
-
-                            // TODO: with proper iter this shouldn't be necessary
-                            if line >= viewport.height {
-                                break 'outer;
-                            }
-                        } else if grapheme == "\t" {
-                            visual_x += (TAB_WIDTH as u16);
-                        } else {
-                            // Cow will prevent allocations if span contained in a single slice
-                            // which should really be the majority case
-                            let grapheme = Cow::from(grapheme);
-                            let width = grapheme_width(&grapheme) as u16;
-
-                            // TODO: this should really happen as an after pass
-                            let style = if visible_selections
-                                .iter()
-                                .any(|range| range.contains(char_index))
-                            {
-                                // cedar
-                                style.clone().bg(Color::Rgb(128, 47, 0))
-                            } else {
-                                style
-                            };
-
-                            let style = if visible_selections
-                                .iter()
-                                .any(|range| range.head == char_index)
-                            {
-                                style.clone().bg(Color::Rgb(255, 255, 255))
-                            } else {
-                                style
-                            };
-
-                            // ugh, improve with a traverse method
-                            // or interleave highlight spans with selection and diagnostic spans
-                            let style = if view.doc.diagnostics.iter().any(|diagnostic| {
-                                diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
-                            }) {
-                                style.clone().add_modifier(Modifier::UNDERLINED)
-                            } else {
-                                style
-                            };
-
-                            // TODO: paint cursor heads except primary
-
-                            self.surface.set_string(
-                                viewport.x + visual_x,
-                                viewport.y + line,
-                                grapheme,
-                                style,
-                            );
-
-                            visual_x += width;
-                        }
-
-                        char_index += 1;
-                    }
-                }
-            }
-        }
-
-        let style: Style = theme.get("ui.linenr");
-        let warning: Style = theme.get("warning");
-        let last_line = view.last_line();
-        for (i, line) in (view.first_line..last_line).enumerate() {
-            if view.doc.diagnostics.iter().any(|d| d.line == line) {
-                self.surface.set_stringn(0, i as u16, "●", 1, warning);
-            }
-
-            self.surface
-                .set_stringn(1, i as u16, format!("{:>5}", line + 1), 5, style);
-        }
-    }
-
-    pub fn render_statusline(&mut self, view: &View, theme: &Theme) {
-        let mode = match view.doc.mode() {
-            Mode::Insert => "INS",
-            Mode::Normal => "NOR",
-            Mode::Goto => "GOTO",
-        };
-        // statusline
-        self.surface.set_style(
-            Rect::new(0, self.size.1 - 2, self.size.0, 1),
-            theme.get("ui.statusline"),
-        );
-        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,
-            format!("{}", view.doc.diagnostics.len()),
-            self.text_color,
-        );
-    }
-
-    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
-            let mut row = 0;
-            let mut col = 0;
-            let max_col = self.size.0 / BASE_WIDTH;
-            let col_height = ((prompt.completion.len() as u16 + max_col - 1) / max_col);
-
-            for i in (3..col_height + 3) {
-                self.surface.set_string(
-                    0,
-                    self.size.1 - i as u16,
-                    " ".repeat(self.size.0 as usize),
-                    self.text_color,
-                );
-            }
-            self.surface.set_style(
-                Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height),
-                theme.get("ui.statusline"),
-            );
-            for (i, command) in prompt.completion.iter().enumerate() {
-                let color = if prompt.completion_selection_index.is_some()
-                    && i == prompt.completion_selection_index.unwrap()
-                {
-                    Style::default().bg(Color::Rgb(104, 060, 232))
-                } else {
-                    self.text_color
-                };
-                self.surface.set_stringn(
-                    1 + col * BASE_WIDTH,
-                    self.size.1 - col_height - 2 + row,
-                    &command,
-                    BASE_WIDTH as usize - 1,
-                    color,
-                );
-                row += 1;
-                if row > col_height - 1 {
-                    row = 0;
-                    col += 1;
-                }
-                if col > max_col {
-                    break;
-                }
-            }
-        }
-        // render buffer text
-        self.surface
-            .set_string(1, self.size.1 - 1, &prompt.prompt, self.text_color);
-        self.surface
-            .set_string(2, self.size.1 - 1, &prompt.line, self.text_color);
-    }
-
-    pub fn draw_and_swap(&mut self) {
-        use tui::backend::Backend;
-        // TODO: theres probably a better place for this
-        self.terminal
-            .backend_mut()
-            .draw(self.cache.diff(&self.surface).into_iter());
-        // swap the buffer
-        std::mem::swap(&mut self.surface, &mut self.cache);
-        self.surface.reset(); // reset is faster than allocating new empty surface
-    }
-
-    pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
-        let mut stdout = stdout();
-        match view.doc.mode() {
-            Mode::Insert => write!(stdout, "\x1B[6 q"),
-            mode => write!(stdout, "\x1B[2 q"),
-        };
-        let pos = if let Some(prompt) = prompt {
-            Position::new(self.size.0 as usize, 2 + prompt.cursor)
-        } else {
-            let cursor = view.doc.state.selection().cursor();
-
-            let mut pos = view
-                .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
-                .expect("Cursor is out of bounds.");
-            pos.col += viewport.x as usize;
-            pos.row += viewport.y as usize;
-            pos
-        };
-
-        execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
-    }
-}
-
-struct EditorView {
-    keymap: Keymaps,
-}
-
-impl EditorView {
-    fn new() -> Self {
-        Self {
-            keymap: keymap::default(),
-        }
-    }
-}
-
-use crate::compositor::Context;
-
-impl Component for EditorView {
-    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) = cx.editor.view_mut() {
-                    view.size = (width, height);
-                    view.ensure_cursor_in_view()
-                };
-                EventResult::Consumed(None)
-            }
-            Event::Key(event) => {
-                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
-                    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) {
-                                command(&mut cx);
-                            } else if let KeyEvent {
-                                code: KeyCode::Char(c),
-                                ..
-                            } = event
-                            {
-                                commands::insert::insert_char(&mut cx, c);
-                            }
-                        }
-                        mode => {
-                            if let Some(command) = self.keymap[&mode].get(&keys) {
-                                command(&mut cx);
-
-                                // TODO: simplistic ensure cursor in view for now
-                            }
-                        }
-                    }
-                    // appease borrowck
-                    let callback = cx.callback.take();
-
-                    view.ensure_cursor_in_view();
-
-                    EventResult::Consumed(callback)
-                } else {
-                    EventResult::Ignored
-                }
-            }
-            Event::Mouse(_) => EventResult::Ignored,
-        }
-    }
-    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 { &*(&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(cx.editor.view().unwrap(), None, viewport);
-    }
-}
-
-impl<'a> Application<'a> {
-    pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
-        let renderer = Renderer::new()?;
         let mut editor = Editor::new();
+        let size = terminal.size()?;
 
         if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
-            editor.open(file, renderer.size)?;
+            editor.open(file, (size.width, size.height))?;
         }
 
         let mut compositor = Compositor::new();
@@ -466,7 +90,7 @@ impl<'a> Application<'a> {
 
         let mut app = Self {
             editor,
-            renderer,
+            terminal,
             // TODO; move to state
             compositor,
 
@@ -478,12 +102,17 @@ impl<'a> Application<'a> {
     }
 
     fn render(&mut self) {
-        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();
+        let executor = &self.executor;
+        let editor = &mut self.editor;
+        let compositor = &self.compositor;
+
+        // TODO: should be unnecessary
+        // self.terminal.autoresize();
+        let mut cx = crate::compositor::Context { editor, executor };
+        let area = self.terminal.size().unwrap();
+        compositor.render(area, self.terminal.current_buffer_mut(), &mut cx);
+
+        self.terminal.draw();
     }
 
     pub async fn event_loop(&mut self) {
@@ -524,7 +153,7 @@ impl<'a> Application<'a> {
         // Handle key events
         let should_redraw = match event {
             Some(Ok(Event::Resize(width, height))) => {
-                self.renderer.resize(width, height);
+                self.terminal.resize(Rect::new(0, 0, width, height));
 
                 self.compositor
                     .handle_event(Event::Resize(width, height), &mut cx)
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index 3cf6bf03..1d94ee63 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -13,10 +13,10 @@
 // Q: how does this work with popups?
 // cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>)
 
-use crate::application::Renderer;
 use crossterm::event::Event;
 use smol::Executor;
 use tui::buffer::Buffer as Surface;
+use tui::layout::Rect;
 
 pub type Callback = Box<dyn Fn(&mut Compositor)>;
 
@@ -36,9 +36,9 @@ pub enum EventResult {
 
 use helix_view::{Editor, View};
 // shared with commands.rs
-pub struct Context<'a, 'b> {
+pub struct Context<'a> {
     pub editor: &'a mut Editor,
-    pub executor: &'a smol::Executor<'b>,
+    pub executor: &'static smol::Executor<'static>,
 }
 
 pub trait Component {
@@ -51,7 +51,7 @@ pub trait Component {
         true
     }
 
-    fn render(&mut self, renderer: &mut Renderer, ctx: &mut Context);
+    fn render(&self, area: Rect, frame: &mut Surface, ctx: &mut Context);
 }
 
 // struct Editor { };
@@ -133,9 +133,9 @@ impl Compositor {
         false
     }
 
-    pub fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) {
-        for layer in &mut self.layers {
-            layer.render(renderer, cx)
+    pub fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+        for layer in &self.layers {
+            layer.render(area, surface, cx)
         }
     }
 }
diff --git a/helix-term/src/editor_view.rs b/helix-term/src/editor_view.rs
new file mode 100644
index 00000000..0181623a
--- /dev/null
+++ b/helix-term/src/editor_view.rs
@@ -0,0 +1,311 @@
+use crate::application::text_color;
+use crate::commands;
+use crate::compositor::{Component, Compositor, EventResult};
+use crate::keymap::{self, Keymaps};
+use crossterm::{
+    cursor,
+    event::{read, Event, EventStream, KeyCode, KeyEvent},
+};
+use helix_view::{document::Mode, Document, Editor, Theme, View};
+use std::borrow::Cow;
+use tui::{
+    backend::CrosstermBackend,
+    buffer::Buffer as Surface,
+    layout::Rect,
+    style::{Color, Modifier, Style},
+};
+
+use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
+
+pub struct EditorView {
+    keymap: Keymaps,
+}
+
+impl EditorView {
+    pub fn new() -> Self {
+        Self {
+            keymap: keymap::default(),
+        }
+    }
+    pub fn render_view(
+        &self,
+        view: &mut View,
+        viewport: Rect,
+        surface: &mut Surface,
+        theme: &Theme,
+    ) {
+        const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
+        let area = Rect::new(OFFSET, 0, viewport.width - OFFSET, viewport.height - 2); // - 2 for statusline and prompt
+        self.render_buffer(view, area, surface, theme);
+        let area = Rect::new(0, viewport.height - 2, viewport.width, 1);
+        self.render_statusline(view, viewport, surface, theme);
+    }
+
+    // TODO: ideally not &mut View but highlights require it because of cursor cache
+    pub fn render_buffer(
+        &self,
+        view: &mut View,
+        viewport: Rect,
+        surface: &mut Surface,
+        theme: &Theme,
+    ) {
+        //  clear with background color
+        surface.set_style(viewport, theme.get("ui.background"));
+
+        // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
+        let source_code = view.doc.text().to_string();
+
+        let last_line = view.last_line();
+
+        let range = {
+            // calculate viewport byte ranges
+            let start = view.doc.text().line_to_byte(view.first_line);
+            let end = view.doc.text().line_to_byte(last_line)
+                + view.doc.text().line(last_line).len_bytes();
+
+            start..end
+        };
+
+        // TODO: range doesn't actually restrict source, just highlight range
+        // TODO: cache highlight results
+        // TODO: only recalculate when state.doc is actually modified
+        let highlights: Vec<_> = match view.doc.syntax.as_mut() {
+            Some(syntax) => {
+                syntax
+                    .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
+                    .unwrap()
+                    .collect() // TODO: we collect here to avoid double borrow, fix later
+            }
+            None => vec![Ok(HighlightEvent::Source {
+                start: range.start,
+                end: range.end,
+            })],
+        };
+        let mut spans = Vec::new();
+        let mut visual_x = 0;
+        let mut line = 0u16;
+        let visible_selections: Vec<Range> = view
+            .doc
+            .state
+            .selection()
+            .ranges()
+            .iter()
+            // TODO: limit selection to one in viewport
+            // .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1))
+            .copied()
+            .collect();
+
+        'outer: for event in highlights {
+            match event.unwrap() {
+                HighlightEvent::HighlightStart(span) => {
+                    spans.push(span);
+                }
+                HighlightEvent::HighlightEnd => {
+                    spans.pop();
+                }
+                HighlightEvent::Source { start, end } => {
+                    // TODO: filter out spans out of viewport for now..
+
+                    let start = view.doc.text().byte_to_char(start);
+                    let end = view.doc.text().byte_to_char(end); // <-- index 744, len 743
+
+                    let text = view.doc.text().slice(start..end);
+
+                    use helix_core::graphemes::{grapheme_width, RopeGraphemes};
+
+                    let style = match spans.first() {
+                        Some(span) => theme.get(theme.scopes()[span.0].as_str()),
+                        None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
+                    };
+
+                    // TODO: we could render the text to a surface, then cache that, that
+                    // way if only the selection/cursor changes we can copy from cache
+                    // and paint the new cursor.
+
+                    let mut char_index = start;
+
+                    // iterate over range char by char
+                    for grapheme in RopeGraphemes::new(&text) {
+                        // TODO: track current char_index
+
+                        if grapheme == "\n" {
+                            visual_x = 0;
+                            line += 1;
+
+                            // TODO: with proper iter this shouldn't be necessary
+                            if line >= viewport.height {
+                                break 'outer;
+                            }
+                        } else if grapheme == "\t" {
+                            visual_x += (TAB_WIDTH as u16);
+                        } else {
+                            // Cow will prevent allocations if span contained in a single slice
+                            // which should really be the majority case
+                            let grapheme = Cow::from(grapheme);
+                            let width = grapheme_width(&grapheme) as u16;
+
+                            // TODO: this should really happen as an after pass
+                            let style = if visible_selections
+                                .iter()
+                                .any(|range| range.contains(char_index))
+                            {
+                                // cedar
+                                style.clone().bg(Color::Rgb(128, 47, 0))
+                            } else {
+                                style
+                            };
+
+                            let style = if visible_selections
+                                .iter()
+                                .any(|range| range.head == char_index)
+                            {
+                                style.clone().bg(Color::Rgb(255, 255, 255))
+                            } else {
+                                style
+                            };
+
+                            // ugh, improve with a traverse method
+                            // or interleave highlight spans with selection and diagnostic spans
+                            let style = if view.doc.diagnostics.iter().any(|diagnostic| {
+                                diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
+                            }) {
+                                style.clone().add_modifier(Modifier::UNDERLINED)
+                            } else {
+                                style
+                            };
+
+                            // TODO: paint cursor heads except primary
+
+                            surface.set_string(
+                                viewport.x + visual_x,
+                                viewport.y + line,
+                                grapheme,
+                                style,
+                            );
+
+                            visual_x += width;
+                        }
+
+                        char_index += 1;
+                    }
+                }
+            }
+        }
+
+        let style: Style = theme.get("ui.linenr");
+        let warning: Style = theme.get("warning");
+        let last_line = view.last_line();
+        for (i, line) in (view.first_line..last_line).enumerate() {
+            if view.doc.diagnostics.iter().any(|d| d.line == line) {
+                surface.set_stringn(0, i as u16, "●", 1, warning);
+            }
+
+            surface.set_stringn(1, i as u16, format!("{:>5}", line + 1), 5, style);
+        }
+    }
+
+    pub fn render_statusline(
+        &self,
+        view: &View,
+        viewport: Rect,
+        surface: &mut Surface,
+        theme: &Theme,
+    ) {
+        let mode = match view.doc.mode() {
+            Mode::Insert => "INS",
+            Mode::Normal => "NOR",
+            Mode::Goto => "GOTO",
+        };
+        // statusline
+        surface.set_style(
+            Rect::new(0, viewport.y, viewport.height, 1),
+            theme.get("ui.statusline"),
+        );
+        surface.set_string(1, viewport.y, mode, text_color());
+
+        if let Some(path) = view.doc.path() {
+            surface.set_string(6, viewport.y, path.to_string_lossy(), text_color());
+        }
+
+        surface.set_string(
+            viewport.width - 10,
+            viewport.y,
+            format!("{}", view.doc.diagnostics.len()),
+            text_color(),
+        );
+    }
+}
+
+use crate::compositor::Context;
+
+impl Component for EditorView {
+    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) = cx.editor.view_mut() {
+                    view.size = (width, height);
+                    view.ensure_cursor_in_view()
+                };
+                EventResult::Consumed(None)
+            }
+            Event::Key(event) => {
+                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
+                    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) {
+                                command(&mut cx);
+                            } else if let KeyEvent {
+                                code: KeyCode::Char(c),
+                                ..
+                            } = event
+                            {
+                                commands::insert::insert_char(&mut cx, c);
+                            }
+                        }
+                        mode => {
+                            if let Some(command) = self.keymap[&mode].get(&keys) {
+                                command(&mut cx);
+
+                                // TODO: simplistic ensure cursor in view for now
+                            }
+                        }
+                    }
+                    // appease borrowck
+                    let callback = cx.callback.take();
+
+                    view.ensure_cursor_in_view();
+
+                    EventResult::Consumed(callback)
+                } else {
+                    EventResult::Ignored
+                }
+            }
+            Event::Mouse(_) => EventResult::Ignored,
+        }
+    }
+
+    fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+        // 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 { &*(&cx.editor.theme as *const Theme) };
+        if let Some(view) = cx.editor.view_mut() {
+            self.render_view(view, area, surface, theme_ref);
+        }
+
+        // TODO: drop unwrap
+        // TODO: !!! self.render_cursor(cx.editor.view().unwrap(), None, viewport);
+    }
+}
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index 92ab10c2..63fbe52d 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -3,8 +3,10 @@
 mod application;
 mod commands;
 mod compositor;
+mod editor_view;
 mod keymap;
 mod prompt;
+mod terminal;
 
 use application::Application;
 
diff --git a/helix-term/src/prompt.rs b/helix-term/src/prompt.rs
index 689eac66..4747c9f5 100644
--- a/helix-term/src/prompt.rs
+++ b/helix-term/src/prompt.rs
@@ -1,9 +1,7 @@
-use crate::{
-    application::Renderer,
-    compositor::{Component, Context, EventResult},
-};
+use crate::compositor::{Component, Context, EventResult};
 use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
 use helix_view::Editor;
+use helix_view::Theme;
 use std::string::String;
 
 pub struct Prompt {
@@ -85,6 +83,68 @@ impl Prompt {
     }
 }
 
+use tui::{
+    buffer::Buffer as Surface,
+    layout::Rect,
+    style::{Color, Modifier, Style},
+};
+
+const BASE_WIDTH: u16 = 30;
+use crate::application::text_color;
+
+impl Prompt {
+    pub fn render_prompt(&self, area: Rect, surface: &mut Surface, theme: &Theme) {
+        // completion
+        if !self.completion.is_empty() {
+            // TODO: find out better way of clearing individual lines of the screen
+            let mut row = 0;
+            let mut col = 0;
+            let max_col = area.width / BASE_WIDTH;
+            let col_height = ((self.completion.len() as u16 + max_col - 1) / max_col);
+
+            for i in (3..col_height + 3) {
+                surface.set_string(
+                    0,
+                    area.height - i as u16,
+                    " ".repeat(area.width as usize),
+                    text_color(),
+                );
+            }
+            surface.set_style(
+                Rect::new(0, area.height - col_height - 2, area.width, col_height),
+                theme.get("ui.statusline"),
+            );
+            for (i, command) in self.completion.iter().enumerate() {
+                let color = if self.completion_selection_index.is_some()
+                    && i == self.completion_selection_index.unwrap()
+                {
+                    Style::default().bg(Color::Rgb(104, 060, 232))
+                } else {
+                    text_color()
+                };
+                surface.set_stringn(
+                    1 + col * BASE_WIDTH,
+                    area.height - col_height - 2 + row,
+                    &command,
+                    BASE_WIDTH as usize - 1,
+                    color,
+                );
+                row += 1;
+                if row > col_height - 1 {
+                    row = 0;
+                    col += 1;
+                }
+                if col > max_col {
+                    break;
+                }
+            }
+        }
+        // render buffer text
+        surface.set_string(1, area.height - 1, &self.prompt, text_color());
+        surface.set_string(2, area.height - 1, &self.line, text_color());
+    }
+}
+
 impl Component for Prompt {
     fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
         let event = match event {
@@ -137,7 +197,7 @@ impl Component for Prompt {
         EventResult::Consumed(None)
     }
 
-    fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) {
-        renderer.render_prompt(self, &cx.editor.theme)
+    fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+        self.render_prompt(area, surface, &cx.editor.theme)
     }
 }
diff --git a/helix-term/src/terminal.rs b/helix-term/src/terminal.rs
new file mode 100644
index 00000000..e40343bd
--- /dev/null
+++ b/helix-term/src/terminal.rs
@@ -0,0 +1,221 @@
+use std::io;
+use tui::{
+    backend::Backend,
+    buffer::Buffer,
+    layout::Rect,
+    widgets::{StatefulWidget, Widget},
+};
+
+#[derive(Debug, Clone, PartialEq)]
+/// UNSTABLE
+enum ResizeBehavior {
+    Fixed,
+    Auto,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+/// UNSTABLE
+pub struct Viewport {
+    area: Rect,
+    resize_behavior: ResizeBehavior,
+}
+
+impl Viewport {
+    /// UNSTABLE
+    pub fn fixed(area: Rect) -> Viewport {
+        Viewport {
+            area,
+            resize_behavior: ResizeBehavior::Fixed,
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+/// Options to pass to [`Terminal::with_options`]
+pub struct TerminalOptions {
+    /// Viewport used to draw to the terminal
+    pub viewport: Viewport,
+}
+
+/// Interface to the terminal backed by Termion
+#[derive(Debug)]
+pub struct Terminal<B>
+where
+    B: Backend,
+{
+    backend: B,
+    /// Holds the results of the current and previous draw calls. The two are compared at the end
+    /// of each draw pass to output the necessary updates to the terminal
+    buffers: [Buffer; 2],
+    /// Index of the current buffer in the previous array
+    current: usize,
+    /// Whether the cursor is currently hidden
+    hidden_cursor: bool,
+    /// Viewport
+    viewport: Viewport,
+}
+
+impl<B> Drop for Terminal<B>
+where
+    B: Backend,
+{
+    fn drop(&mut self) {
+        // Attempt to restore the cursor state
+        if self.hidden_cursor {
+            if let Err(err) = self.show_cursor() {
+                eprintln!("Failed to show the cursor: {}", err);
+            }
+        }
+    }
+}
+
+impl<B> Terminal<B>
+where
+    B: Backend,
+{
+    /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and
+    /// default colors for the foreground and the background
+    pub fn new(backend: B) -> io::Result<Terminal<B>> {
+        let size = backend.size()?;
+        Terminal::with_options(
+            backend,
+            TerminalOptions {
+                viewport: Viewport {
+                    area: size,
+                    resize_behavior: ResizeBehavior::Auto,
+                },
+            },
+        )
+    }
+
+    /// UNSTABLE
+    pub fn with_options(backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
+        Ok(Terminal {
+            backend,
+            buffers: [
+                Buffer::empty(options.viewport.area),
+                Buffer::empty(options.viewport.area),
+            ],
+            current: 0,
+            hidden_cursor: false,
+            viewport: options.viewport,
+        })
+    }
+
+    // /// Get a Frame object which provides a consistent view into the terminal state for rendering.
+    // pub fn get_frame(&mut self) -> Frame<B> {
+    //     Frame {
+    //         terminal: self,
+    //         cursor_position: None,
+    //     }
+    // }
+
+    pub fn current_buffer_mut(&mut self) -> &mut Buffer {
+        &mut self.buffers[self.current]
+    }
+
+    pub fn backend(&self) -> &B {
+        &self.backend
+    }
+
+    pub fn backend_mut(&mut self) -> &mut B {
+        &mut self.backend
+    }
+
+    /// Obtains a difference between the previous and the current buffer and passes it to the
+    /// current backend for drawing.
+    pub fn flush(&mut self) -> io::Result<()> {
+        let previous_buffer = &self.buffers[1 - self.current];
+        let current_buffer = &self.buffers[self.current];
+        let updates = previous_buffer.diff(current_buffer);
+        self.backend.draw(updates.into_iter())
+    }
+
+    /// Updates the Terminal so that internal buffers match the requested size. Requested size will
+    /// be saved so the size can remain consistent when rendering.
+    /// This leads to a full clear of the screen.
+    pub fn resize(&mut self, area: Rect) -> io::Result<()> {
+        self.buffers[self.current].resize(area);
+        self.buffers[1 - self.current].resize(area);
+        self.viewport.area = area;
+        self.clear()
+    }
+
+    /// Queries the backend for size and resizes if it doesn't match the previous size.
+    pub fn autoresize(&mut self) -> io::Result<()> {
+        if self.viewport.resize_behavior == ResizeBehavior::Auto {
+            let size = self.size()?;
+            if size != self.viewport.area {
+                self.resize(size)?;
+            }
+        };
+        Ok(())
+    }
+
+    /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state
+    /// and prepares for the next draw call.
+    pub fn draw(&mut self) -> io::Result<()> {
+        // // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
+        // // and the terminal (if growing), which may OOB.
+        // self.autoresize()?;
+
+        // let mut frame = self.get_frame();
+        // f(&mut frame);
+        // // We can't change the cursor position right away because we have to flush the frame to
+        // // stdout first. But we also can't keep the frame around, since it holds a &mut to
+        // // Terminal. Thus, we're taking the important data out of the Frame and dropping it.
+        // let cursor_position = frame.cursor_position;
+
+        // Draw to stdout
+        self.flush()?;
+
+        // match cursor_position {
+        //     None => self.hide_cursor()?,
+        //     Some((x, y)) => {
+        //         self.show_cursor()?;
+        //         self.set_cursor(x, y)?;
+        //     }
+        // }
+
+        // Swap buffers
+        self.buffers[1 - self.current].reset();
+        self.current = 1 - self.current;
+
+        // Flush
+        self.backend.flush()?;
+        Ok(())
+    }
+
+    pub fn hide_cursor(&mut self) -> io::Result<()> {
+        self.backend.hide_cursor()?;
+        self.hidden_cursor = true;
+        Ok(())
+    }
+
+    pub fn show_cursor(&mut self) -> io::Result<()> {
+        self.backend.show_cursor()?;
+        self.hidden_cursor = false;
+        Ok(())
+    }
+
+    pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
+        self.backend.get_cursor()
+    }
+
+    pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
+        self.backend.set_cursor(x, y)
+    }
+
+    /// Clear the terminal and force a full redraw on the next draw call.
+    pub fn clear(&mut self) -> io::Result<()> {
+        self.backend.clear()?;
+        // Reset the back buffer to make sure the next update will redraw everything.
+        self.buffers[1 - self.current].reset();
+        Ok(())
+    }
+
+    /// Queries the real size of the backend.
+    pub fn size(&self) -> io::Result<Rect> {
+        self.backend.size()
+    }
+}

From ef0d062b1fd202fe89bc4bbd33826c46f660ef70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sun, 13 Dec 2020 13:29:34 +0900
Subject: [PATCH 7/9] Fix cursor positioning.

---
 helix-term/src/application.rs | 30 ++++--------------------------
 helix-term/src/component.rs   | 20 --------------------
 helix-term/src/compositor.rs  | 14 ++++++++++++++
 helix-term/src/editor_view.rs | 24 ++++++++++++++++++++----
 helix-term/src/prompt.rs      |  8 ++++++++
 5 files changed, 46 insertions(+), 50 deletions(-)
 delete mode 100644 helix-term/src/component.rs

diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 7a74f8ba..c25871c7 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -9,7 +9,6 @@ use crate::prompt::Prompt;
 use log::{debug, info};
 
 use std::{
-    borrow::Cow,
     io::{self, stdout, Stdout, Write},
     path::PathBuf,
     time::Duration,
@@ -47,31 +46,9 @@ pub struct Application {
 // TODO: temp
 #[inline(always)]
 pub fn text_color() -> Style {
-    return Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
+    Style::default().fg(Color::Rgb(219, 191, 239)) // lilac
 }
 
-// pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
-//     let mut stdout = stdout();
-//     match view.doc.mode() {
-//         Mode::Insert => write!(stdout, "\x1B[6 q"),
-//         mode => write!(stdout, "\x1B[2 q"),
-//     };
-//     let pos = if let Some(prompt) = prompt {
-//         Position::new(self.size.0 as usize, 2 + prompt.cursor)
-//     } else {
-//         let cursor = view.doc.state.selection().cursor();
-
-//         let mut pos = view
-//             .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
-//             .expect("Cursor is out of bounds.");
-//         pos.col += viewport.x as usize;
-//         pos.row += viewport.y as usize;
-//         pos
-//     };
-
-//     execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
-// }
-
 impl Application {
     pub fn new(mut args: Args, executor: &'static smol::Executor<'static>) -> Result<Self, Error> {
         let backend = CrosstermBackend::new(stdout());
@@ -106,13 +83,14 @@ impl Application {
         let editor = &mut self.editor;
         let compositor = &self.compositor;
 
-        // TODO: should be unnecessary
-        // self.terminal.autoresize();
         let mut cx = crate::compositor::Context { editor, executor };
         let area = self.terminal.size().unwrap();
+
         compositor.render(area, self.terminal.current_buffer_mut(), &mut cx);
+        let pos = compositor.cursor_position(area, &mut cx);
 
         self.terminal.draw();
+        self.terminal.set_cursor(pos.col as u16, pos.row as u16);
     }
 
     pub async fn event_loop(&mut self) {
diff --git a/helix-term/src/component.rs b/helix-term/src/component.rs
deleted file mode 100644
index 08d6c620..00000000
--- a/helix-term/src/component.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-// IDEA: render to a cache buffer, then if not changed, copy the buf into the parent
-type Surface = ();
-pub trait Component {
-    /// Process input events, return true if handled.
-    fn process_event(&mut self, event: crossterm::event::Event, args: ()) -> bool;
-    /// Should redraw? Useful for saving redraw cycles if we know component didn't change.
-    fn should_update(&self) -> bool {
-        true
-    }
-
-    fn render(&mut self, surface: &mut Surface, args: ());
-}
-
-// HStack / VStack
-// focus by component id: each View/Editor gets it's own incremental id at create
-// Component: View(Arc<State>) -> multiple views can point to same state
-// id 0 = prompt?
-// when entering to prompt, it needs to direct Commands to last focus window
-// -> prompt.trigger(focus_id), on_leave -> focus(focus_id)
-// popups on another layer
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index 1d94ee63..2e65f02a 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -14,6 +14,7 @@
 // cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>)
 
 use crossterm::event::Event;
+use helix_core::Position;
 use smol::Executor;
 use tui::buffer::Buffer as Surface;
 use tui::layout::Rect;
@@ -52,6 +53,10 @@ pub trait Component {
     }
 
     fn render(&self, area: Rect, frame: &mut Surface, ctx: &mut Context);
+
+    fn cursor_position(&self, area: Rect, ctx: &mut Context) -> Option<Position> {
+        None
+    }
 }
 
 // struct Editor { };
@@ -138,4 +143,13 @@ impl Compositor {
             layer.render(area, surface, cx)
         }
     }
+
+    pub fn cursor_position(&self, area: Rect, cx: &mut Context) -> Position {
+        for layer in self.layers.iter().rev() {
+            if let Some(pos) = layer.cursor_position(area, cx) {
+                return pos;
+            }
+        }
+        panic!("No layer returned a position!");
+    }
 }
diff --git a/helix-term/src/editor_view.rs b/helix-term/src/editor_view.rs
index 0181623a..b778e79b 100644
--- a/helix-term/src/editor_view.rs
+++ b/helix-term/src/editor_view.rs
@@ -21,6 +21,8 @@ pub struct EditorView {
     keymap: Keymaps,
 }
 
+const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
+
 impl EditorView {
     pub fn new() -> Self {
         Self {
@@ -34,11 +36,10 @@ impl EditorView {
         surface: &mut Surface,
         theme: &Theme,
     ) {
-        const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
         let area = Rect::new(OFFSET, 0, viewport.width - OFFSET, viewport.height - 2); // - 2 for statusline and prompt
         self.render_buffer(view, area, surface, theme);
         let area = Rect::new(0, viewport.height - 2, viewport.width, 1);
-        self.render_statusline(view, viewport, surface, theme);
+        self.render_statusline(view, area, surface, theme);
     }
 
     // TODO: ideally not &mut View but highlights require it because of cursor cache
@@ -218,7 +219,7 @@ impl EditorView {
         };
         // statusline
         surface.set_style(
-            Rect::new(0, viewport.y, viewport.height, 1),
+            Rect::new(0, viewport.y, viewport.width, 1),
             theme.get("ui.statusline"),
         );
         surface.set_string(1, viewport.y, mode, text_color());
@@ -306,6 +307,21 @@ impl Component for EditorView {
         }
 
         // TODO: drop unwrap
-        // TODO: !!! self.render_cursor(cx.editor.view().unwrap(), None, viewport);
+    }
+
+    fn cursor_position(&self, area: Rect, ctx: &mut Context) -> Option<Position> {
+        // match view.doc.mode() {
+        //     Mode::Insert => write!(stdout, "\x1B[6 q"),
+        //     mode => write!(stdout, "\x1B[2 q"),
+        // };
+        let view = ctx.editor.view().unwrap();
+        let cursor = view.doc.state.selection().cursor();
+
+        let mut pos = view
+            .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
+            .expect("Cursor is out of bounds.");
+        pos.col += area.x as usize + OFFSET as usize;
+        pos.row += area.y as usize;
+        Some(pos)
     }
 }
diff --git a/helix-term/src/prompt.rs b/helix-term/src/prompt.rs
index 4747c9f5..7f473ebc 100644
--- a/helix-term/src/prompt.rs
+++ b/helix-term/src/prompt.rs
@@ -1,5 +1,6 @@
 use crate::compositor::{Component, Context, EventResult};
 use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
+use helix_core::Position;
 use helix_view::Editor;
 use helix_view::Theme;
 use std::string::String;
@@ -200,4 +201,11 @@ impl Component for Prompt {
     fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
         self.render_prompt(area, surface, &cx.editor.theme)
     }
+
+    fn cursor_position(&self, area: Rect, ctx: &mut Context) -> Option<Position> {
+        Some(Position::new(
+            area.height as usize - 1,
+            area.x as usize + 2 + self.cursor,
+        ))
+    }
 }

From 7dc24a25ba148a9cd7c936e02cc03873ed6a467b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sun, 13 Dec 2020 13:35:30 +0900
Subject: [PATCH 8/9] Move ui modules under a ui:: namespace.

---
 helix-term/src/application.rs                 | 33 +++++--------------
 helix-term/src/commands.rs                    |  2 +-
 helix-term/src/helix.log                      |  0
 helix-term/src/main.rs                        |  3 +-
 .../src/{editor_view.rs => ui/editor.rs}      | 22 ++++++-------
 helix-term/src/ui/mod.rs                      | 14 ++++++++
 helix-term/src/{ => ui}/prompt.rs             | 11 ++++---
 7 files changed, 42 insertions(+), 43 deletions(-)
 create mode 100644 helix-term/src/helix.log
 rename helix-term/src/{editor_view.rs => ui/editor.rs} (98%)
 create mode 100644 helix-term/src/ui/mod.rs
 rename helix-term/src/{ => ui}/prompt.rs (97%)

diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index c25871c7..dc37612a 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -2,9 +2,8 @@ use clap::ArgMatches as Args;
 
 use helix_view::{document::Mode, Document, Editor, Theme, View};
 
-use crate::compositor::{Component, Compositor, EventResult};
-use crate::editor_view::EditorView;
-use crate::prompt::Prompt;
+use crate::compositor::Compositor;
+use crate::ui;
 
 use log::{debug, info};
 
@@ -19,18 +18,11 @@ use smol::prelude::*;
 use anyhow::Error;
 
 use crossterm::{
-    cursor,
-    event::{read, Event, EventStream, KeyCode, KeyEvent},
-    execute, queue,
-    terminal::{self, disable_raw_mode, enable_raw_mode},
+    event::{Event, EventStream},
+    execute, terminal,
 };
 
-use tui::{
-    backend::CrosstermBackend,
-    buffer::Buffer as Surface,
-    layout::Rect,
-    style::{Color, Modifier, Style},
-};
+use tui::{backend::CrosstermBackend, layout::Rect};
 
 type Terminal = crate::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
 
@@ -43,12 +35,6 @@ pub struct Application {
     language_server: helix_lsp::Client,
 }
 
-// TODO: temp
-#[inline(always)]
-pub fn text_color() -> Style {
-    Style::default().fg(Color::Rgb(219, 191, 239)) // lilac
-}
-
 impl Application {
     pub fn new(mut args: Args, executor: &'static smol::Executor<'static>) -> Result<Self, Error> {
         let backend = CrosstermBackend::new(stdout());
@@ -61,14 +47,13 @@ impl Application {
         }
 
         let mut compositor = Compositor::new();
-        compositor.push(Box::new(EditorView::new()));
+        compositor.push(Box::new(ui::EditorView::new()));
 
         let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
 
         let mut app = Self {
             editor,
             terminal,
-            // TODO; move to state
             compositor,
 
             executor,
@@ -213,7 +198,7 @@ impl Application {
     }
 
     pub async fn run(&mut self) -> Result<(), Error> {
-        enable_raw_mode()?;
+        terminal::enable_raw_mode()?;
 
         let mut stdout = stdout();
 
@@ -223,7 +208,7 @@ impl Application {
         let hook = std::panic::take_hook();
         std::panic::set_hook(Box::new(move |info| {
             execute!(std::io::stdout(), terminal::LeaveAlternateScreen);
-            disable_raw_mode();
+            terminal::disable_raw_mode();
             hook(info);
         }));
 
@@ -234,7 +219,7 @@ impl Application {
 
         execute!(stdout, terminal::LeaveAlternateScreen)?;
 
-        disable_raw_mode()?;
+        terminal::disable_raw_mode()?;
 
         Ok(())
     }
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 04482ef7..b345d2e8 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -10,7 +10,7 @@ use helix_core::{
 use once_cell::sync::Lazy;
 
 use crate::compositor::Compositor;
-use crate::prompt::Prompt;
+use crate::ui::Prompt;
 
 use helix_view::{
     document::Mode,
diff --git a/helix-term/src/helix.log b/helix-term/src/helix.log
new file mode 100644
index 00000000..e69de29b
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index 63fbe52d..f350b4c1 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -3,10 +3,9 @@
 mod application;
 mod commands;
 mod compositor;
-mod editor_view;
 mod keymap;
-mod prompt;
 mod terminal;
+mod ui;
 
 use application::Application;
 
diff --git a/helix-term/src/editor_view.rs b/helix-term/src/ui/editor.rs
similarity index 98%
rename from helix-term/src/editor_view.rs
rename to helix-term/src/ui/editor.rs
index b778e79b..ceb5a442 100644
--- a/helix-term/src/editor_view.rs
+++ b/helix-term/src/ui/editor.rs
@@ -1,13 +1,16 @@
-use crate::application::text_color;
 use crate::commands;
-use crate::compositor::{Component, Compositor, EventResult};
+use crate::compositor::{Component, Compositor, Context, EventResult};
 use crate::keymap::{self, Keymaps};
+use crate::ui::text_color;
+
+use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
+use helix_view::{document::Mode, Document, Editor, Theme, View};
+use std::borrow::Cow;
+
 use crossterm::{
     cursor,
     event::{read, Event, EventStream, KeyCode, KeyEvent},
 };
-use helix_view::{document::Mode, Document, Editor, Theme, View};
-use std::borrow::Cow;
 use tui::{
     backend::CrosstermBackend,
     buffer::Buffer as Surface,
@@ -15,8 +18,6 @@ use tui::{
     style::{Color, Modifier, Style},
 };
 
-use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
-
 pub struct EditorView {
     keymap: Keymaps,
 }
@@ -212,6 +213,7 @@ impl EditorView {
         surface: &mut Surface,
         theme: &Theme,
     ) {
+        let text_color = text_color();
         let mode = match view.doc.mode() {
             Mode::Insert => "INS",
             Mode::Normal => "NOR",
@@ -222,23 +224,21 @@ impl EditorView {
             Rect::new(0, viewport.y, viewport.width, 1),
             theme.get("ui.statusline"),
         );
-        surface.set_string(1, viewport.y, mode, text_color());
+        surface.set_string(1, viewport.y, mode, text_color);
 
         if let Some(path) = view.doc.path() {
-            surface.set_string(6, viewport.y, path.to_string_lossy(), text_color());
+            surface.set_string(6, viewport.y, path.to_string_lossy(), text_color);
         }
 
         surface.set_string(
             viewport.width - 10,
             viewport.y,
             format!("{}", view.doc.diagnostics.len()),
-            text_color(),
+            text_color,
         );
     }
 }
 
-use crate::compositor::Context;
-
 impl Component for EditorView {
     fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
         match event {
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
new file mode 100644
index 00000000..bc79e09c
--- /dev/null
+++ b/helix-term/src/ui/mod.rs
@@ -0,0 +1,14 @@
+mod editor;
+mod prompt;
+
+pub use editor::EditorView;
+pub use prompt::Prompt;
+
+pub use tui::layout::Rect;
+pub use tui::style::{Color, Modifier, Style};
+
+// TODO: temp
+#[inline(always)]
+pub fn text_color() -> Style {
+    Style::default().fg(Color::Rgb(219, 191, 239)) // lilac
+}
diff --git a/helix-term/src/prompt.rs b/helix-term/src/ui/prompt.rs
similarity index 97%
rename from helix-term/src/prompt.rs
rename to helix-term/src/ui/prompt.rs
index 7f473ebc..071cac90 100644
--- a/helix-term/src/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -91,10 +91,11 @@ use tui::{
 };
 
 const BASE_WIDTH: u16 = 30;
-use crate::application::text_color;
+use crate::ui::text_color;
 
 impl Prompt {
     pub fn render_prompt(&self, area: Rect, surface: &mut Surface, theme: &Theme) {
+        let text_color = text_color();
         // completion
         if !self.completion.is_empty() {
             // TODO: find out better way of clearing individual lines of the screen
@@ -108,7 +109,7 @@ impl Prompt {
                     0,
                     area.height - i as u16,
                     " ".repeat(area.width as usize),
-                    text_color(),
+                    text_color,
                 );
             }
             surface.set_style(
@@ -121,7 +122,7 @@ impl Prompt {
                 {
                     Style::default().bg(Color::Rgb(104, 060, 232))
                 } else {
-                    text_color()
+                    text_color
                 };
                 surface.set_stringn(
                     1 + col * BASE_WIDTH,
@@ -141,8 +142,8 @@ impl Prompt {
             }
         }
         // render buffer text
-        surface.set_string(1, area.height - 1, &self.prompt, text_color());
-        surface.set_string(2, area.height - 1, &self.line, text_color());
+        surface.set_string(1, area.height - 1, &self.prompt, text_color);
+        surface.set_string(2, area.height - 1, &self.line, text_color);
     }
 }
 

From 07801b60bccd0f084eae925e0290c24322de575f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sun, 13 Dec 2020 13:57:28 +0900
Subject: [PATCH 9/9] Remove the prompt on ESC.

---
 helix-term/src/ui/prompt.rs | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 071cac90..ce00a129 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -1,4 +1,4 @@
-use crate::compositor::{Component, Context, EventResult};
+use crate::compositor::{Component, Compositor, Context, EventResult};
 use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
 use helix_core::Position;
 use helix_view::Editor;
@@ -161,7 +161,12 @@ impl Component for Prompt {
             } => self.insert_char(c),
             KeyEvent {
                 code: KeyCode::Esc, ..
-            } => self.should_close = true,
+            } => {
+                return EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor| {
+                    // remove the layer
+                    compositor.pop();
+                })));
+            }
             KeyEvent {
                 code: KeyCode::Right,
                 ..