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] 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,
+        ))
+    }
 }