@@ -1,6 +1,6 @@
1use clap::ArgMatches as Args;
1use clap::ArgMatches as Args;
2use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State};
2use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State};
3−use helix_view::{commands, keymap, prompt::Prompt, View};
3+use helix_view::{commands, keymap, prompt::Prompt, Editor, View};
6 borrow::Cow,
6 borrow::Cow,
@@ -18,11 +18,15 @@ use crossterm::{
18 cursor::position,
18 cursor::position,
19 event::{self, read, Event, EventStream, KeyCode, KeyEvent},
19 event::{self, read, Event, EventStream, KeyCode, KeyEvent},
20 execute, queue,
20 execute, queue,
21− style::{Color, Print, SetForegroundColor},
22 terminal::{self, disable_raw_mode, enable_raw_mode},
21 terminal::{self, disable_raw_mode, enable_raw_mode},
25−use tui::{backend::CrosstermBackend, buffer::Buffer as Surface, layout::Rect, style::Style};
25+ backend::CrosstermBackend,
26+ buffer::Buffer as Surface,
28+ style::{Color, Style},
27const OFFSET: u16 = 6; // 5 linenr + 1 gutter
31const OFFSET: u16 = 6; // 5 linenr + 1 gutter
@@ -30,78 +34,54 @@ type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
31static EX: smol::Executor = smol::Executor::new();
35static EX: smol::Executor = smol::Executor::new();
33−pub struct Editor {
37+pub struct Application {
34− terminal: Terminal,
38+ editor: Editor,
35− view: Option<View>,
39+ prompt: Option<Prompt>,
36 size: (u16, u16),
45 size: (u16, u16),
37 surface: Surface,
47 surface: Surface,
38 cache: Surface,
48 cache: Surface,
39− prompt: Option<Prompt>,
49+ text_color: Style,
43−impl Editor {
52+impl Renderer {
44− pub fn new(mut args: Args) -> Result<Self, Error> {
53+ pub fn new() -> Result<Self, Error> {
45 let backend = CrosstermBackend::new(stdout());
54 let backend = CrosstermBackend::new(stdout());
47 let mut terminal = Terminal::new(backend)?;
55 let mut terminal = Terminal::new(backend)?;
48 let size = terminal::size().unwrap();
56 let size = terminal::size().unwrap();
57+ let text_color: Style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
49 let area = Rect::new(0, 0, size.0, size.1);
59 let area = Rect::new(0, 0, size.0, size.1);
51− let mut editor = Editor {
61+ Ok(Self {
55 surface: Surface::empty(area),
64 surface: Surface::empty(area),
56 cache: Surface::empty(area),
65 cache: Surface::empty(area),
57− // TODO; move to state
66+ text_color,
62− if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
69− pub fn set_prompt(self) {
70− let commands = |input: &str| match input {
71− "q" => self.should_close = true,
74− let prompt = Prompt::new(|input| None, commands);
75− self.prompt = Some(prompt);
78− pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
70+ pub fn resize(&mut self, width: u16, height: u16) {
79− self.view = Some(View::open(path, self.size)?);
71+ self.size = (width, height);
80− Ok(())
72+ let area = Rect::new(0, 0, width, height);
81− }
73+ self.surface = Surface::empty(area);
82−
74+ self.cache = Surface::empty(area);
83− fn render(&mut self) {
84− use tui::style::Color;
85− // TODO: ideally not mut but highlights require it because of cursor cache
86− let viewport = Rect::new(OFFSET, 0, self.size.0, self.size.1 - 2); // - 2 for statusline and prompt
87− let text_color: Style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
89− self.render_view(viewport, text_color);
91− self.render_prompt(text_color);
93− self.render_cursor(viewport, text_color);
96− pub fn render_view(&mut self, viewport: Rect, text_color: Style) {
77+ pub fn render_view(&mut self, view: &mut View, viewport: Rect) {
97− self.render_buffer(viewport);
78+ self.render_buffer(view, viewport);
98− self.render_statusline(text_color);
79+ self.render_statusline(view);
82+ // TODO: ideally not &mut View but highlights require it because of cursor cache
101− pub fn render_buffer(&mut self, viewport: Rect) {
83+ pub fn render_buffer(&mut self, view: &mut View, viewport: Rect) {
102− use tui::style::Color;
103 let area = Rect::new(0, 0, self.size.0, self.size.1);
84 let area = Rect::new(0, 0, self.size.0, self.size.1);
104− let mut view: &mut View = self.view.as_mut().unwrap();
105 self.surface.reset(); // reset is faster than allocating new empty surface
85 self.surface.reset(); // reset is faster than allocating new empty surface
107 // clear with background color
87 // clear with background color
@@ -241,8 +221,7 @@ impl Editor {
244− pub fn render_statusline(&mut self, text_color: Style) {
224+ pub fn render_statusline(&mut self, view: &View) {
245− let view = self.view.as_ref().unwrap();
246 let mode = match view.state.mode() {
225 let mode = match view.state.mode() {
247 Mode::Insert => "INS",
226 Mode::Insert => "INS",
248 Mode::Normal => "NOR",
227 Mode::Normal => "NOR",
@@ -255,24 +234,16 @@ impl Editor {
255 view.theme.get("ui.statusline"),
234 view.theme.get("ui.statusline"),
257 self.surface
236 self.surface
258− .set_string(1, self.size.1 - 2, mode, text_color);
237+ .set_string(1, self.size.1 - 2, mode, self.text_color);
261− pub fn render_prompt(&mut self, text_color: Style) {
240+ pub fn render_prompt(&mut self, prompt: &Prompt) {
262− // TODO: maybe name this render_commandline
263 use tui::backend::Backend;
241 use tui::backend::Backend;
264− let view = self.view.as_ref().unwrap();
265 // render buffer text
242 // render buffer text
266− let buffer_string;
243+ self.surface
267− if view.state.mode == Mode::Command {
244+ .set_string(1, self.size.1 - 1, String::from(":"), self.text_color);
268− buffer_string = &self.prompt.unwrap().buffer;
245+ self.surface
269− self.surface
246+ .set_string(2, self.size.1 - 1, &prompt.buffer, self.text_color);
270− .set_string(1, self.size.1 - 1, String::from(":"), text_color);
272− .set_string(2, self.size.1 - 1, buffer_string, text_color);
274− buffer_string = &String::from("");
277 // TODO: theres probably a better place for this
248 // TODO: theres probably a better place for this
278 self.terminal
249 self.terminal
@@ -282,33 +253,85 @@ impl Editor {
282 std::mem::swap(&mut self.surface, &mut self.cache);
253 std::mem::swap(&mut self.surface, &mut self.cache);
285− pub fn render_cursor(&mut self, viewport: Rect, text_color: Style) {
256+ pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
286− let mut pos: Position;
287− let view = self.view.as_ref().unwrap();
288 let mut stdout = stdout();
257 let mut stdout = stdout();
289 match view.state.mode() {
258 match view.state.mode() {
290 Mode::Insert => write!(stdout, "\x1B[6 q"),
259 Mode::Insert => write!(stdout, "\x1B[6 q"),
291 mode => write!(stdout, "\x1B[2 q"),
260 mode => write!(stdout, "\x1B[2 q"),
293− if view.state.mode() == Mode::Command {
262+ let pos = if let Some(prompt) = prompt {
294− pos = Position::new(self.size.0 as usize, 2 + self.prompt.unwrap().cursor_loc);
263+ Position::new(self.size.0 as usize, 2 + prompt.cursor_loc)
296 if let Some(path) = view.state.path() {
265 if let Some(path) = view.state.path() {
297− self.surface
266+ self.surface.set_string(
298− .set_string(6, self.size.1 - 1, path.to_string_lossy(), text_color);
269+ path.to_string_lossy(),
301 let cursor = view.state.selection().cursor();
274 let cursor = view.state.selection().cursor();
303− pos = view
276+ let mut pos = view
304 .screen_coords_at_pos(&view.state.doc().slice(..), cursor)
277 .screen_coords_at_pos(&view.state.doc().slice(..), cursor)
305 .expect("Cursor is out of bounds.");
278 .expect("Cursor is out of bounds.");
306 pos.col += viewport.x as usize;
279 pos.col += viewport.x as usize;
307 pos.row += viewport.y as usize;
280 pos.row += viewport.y as usize;
310 execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
284 execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
289+ pub fn new(mut args: Args) -> Result<Self, Error> {
290+ let terminal = Renderer::new()?;
291+ let mut editor = Editor::new();
293+ if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
294+ editor.open(file, terminal.size)?;
297+ let mut app = Self {
300+ // TODO; move to state
302+ should_close: false,
308+ pub fn set_prompt(&mut self) {
309+ // let commands = |input| match input {
310+ // "q" => self.should_close = true,
313+ // let prompt = Prompt::new(|input| None, commands);
314+ // self.prompt = Some(prompt);
317+ fn render(&mut self) {
318+ let viewport = Rect::new(OFFSET, 0, self.terminal.size.0, self.terminal.size.1 - 2); // - 2 for statusline and prompt
320+ if let Some(view) = &mut self.editor.view {
321+ self.terminal.render_view(view, viewport);
324+ if let Some(prompt) = &self.prompt {
325+ self.terminal.render_prompt(prompt);
328+ // TODO: drop unwrap
329+ self.terminal.render_cursor(
330+ self.editor.view.as_ref().unwrap(),
331+ self.prompt.as_ref(),
313 pub async fn event_loop(&mut self) {
336 pub async fn event_loop(&mut self) {
314 let mut reader = EventStream::new();
337 let mut reader = EventStream::new();
@@ -325,14 +348,11 @@ impl Editor {
325 let mut event = reader.next().await;
348 let mut event = reader.next().await;
326 match event {
349 match event {
327 Some(Ok(Event::Resize(width, height))) => {
350 Some(Ok(Event::Resize(width, height))) => {
328− self.size = (width, height);
351+ self.terminal.resize(width, height);
329− let area = Rect::new(0, 0, width, height);
330− self.surface = Surface::empty(area);
331− self.cache = Surface::empty(area);
333 // TODO: simplistic ensure cursor in view for now
353 // TODO: simplistic ensure cursor in view for now
334− if let Some(view) = &mut self.view {
354+ if let Some(view) = &mut self.editor.view {
335− view.size = self.size;
355+ view.size = self.terminal.size;
336 view.ensure_cursor_in_view()
356 view.ensure_cursor_in_view()
@@ -341,7 +361,7 @@ impl Editor {
341 Some(Ok(Event::Key(event))) => {
361 Some(Ok(Event::Key(event))) => {
342 // TODO: sequences (`gg`)
362 // TODO: sequences (`gg`)
343 // TODO: handle count other than 1
363 // TODO: handle count other than 1
344− if let Some(view) = &mut self.view {
364+ if let Some(view) = &mut self.editor.view {
345 let keys = vec![event];
365 let keys = vec![event];
346 match view.state.mode() {
366 match view.state.mode() {
347 Mode::Insert => {
367 Mode::Insert => {
@@ -357,7 +377,7 @@ impl Editor {
357 view.ensure_cursor_in_view();
377 view.ensure_cursor_in_view();
359 Mode::Command => {
379 Mode::Command => {
360− self.prompt.unwrap().handle_input(event, view);
380+ self.prompt.as_mut().unwrap().handle_input(event, view);
362 mode => {
382 mode => {
363 if let Some(command) = keymap[&mode].get(&keys) {
383 if let Some(command) = keymap[&mode].get(&keys) {