on master help sign in
Refactor Editor into Application and Editor/Workspace.
49cc6c1Blaž Hrastnik
helix-term/src/editor.rs → helix-term/src/application.rsrenamed+108−88
@@ -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};
4
4
5use std::{
5use std::{
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},
23};
22};
24
23
24+use tui::{
25−use tui::{backend::CrosstermBackend, buffer::Buffer as Surface, layout::Rect, style::Style};
25+ backend::CrosstermBackend,
26+ buffer::Buffer as Surface,
27+ layout::Rect,
28+ style::{Color, Style},
29+};
26
30
27const OFFSET: u16 = 6; // 5 linenr + 1 gutter
31const OFFSET: u16 = 6; // 5 linenr + 1 gutter
28
32
@@ -30,78 +34,54 @@ type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
30
34
31static EX: smol::Executor = smol::Executor::new();
35static EX: smol::Executor = smol::Executor::new();
32
36
33−pub struct Editor {
37+pub struct Application {
34− terminal: Terminal,
38+ editor: Editor,
35− view: Option<View>,
39+ prompt: Option<Prompt>,
40+ should_close: bool,
41+ terminal: Renderer,
42+}
43+
44+struct Renderer {
36 size: (u16, u16),
45 size: (u16, u16),
46+ terminal: Terminal,
37 surface: Surface,
47 surface: Surface,
38 cache: Surface,
48 cache: Surface,
39− prompt: Option<Prompt>,
49+ text_color: Style,
40− should_close: bool,
41}
50}
42
51
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());
46−
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
58+
49 let area = Rect::new(0, 0, size.0, size.1);
59 let area = Rect::new(0, 0, size.0, size.1);
50
60
51− let mut editor = Editor {
61+ Ok(Self {
52− terminal,
53− view: None,
54 size,
62 size,
63+ terminal,
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,
58− prompt: None,
59− should_close: false,
60− };
61−
62− if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
63− editor.open(file)?;
64− }
65−
66− Ok(editor)
67− }
68−
69− pub fn set_prompt(self) {
70− let commands = |input: &str| match input {
71− "q" => self.should_close = true,
72− _ => (),
73− };
67+ })
74− let prompt = Prompt::new(|input| None, commands);
75− self.prompt = Some(prompt);
76 }
68 }
77
69
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
88−
89− self.render_view(viewport, text_color);
90−
91− self.render_prompt(text_color);
92−
93− self.render_cursor(viewport, text_color);
94 }
75 }
95
76
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);
99 }
80 }
100
81
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
106
86
107 // clear with background color
87 // clear with background color
@@ -241,8 +221,7 @@ impl Editor {
241 }
221 }
242 }
222 }
243
223
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"),
256 );
235 );
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);
259 }
238 }
260
239
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);
271− self.surface
272− .set_string(2, self.size.1 - 1, buffer_string, text_color);
273− } else {
274− buffer_string = &String::from("");
275− }
276
247
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);
283 }
254 }
284
255
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"),
292 };
261 };
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)
295 } else {
264 } else {
296 if let Some(path) = view.state.path() {
265 if let Some(path) = view.state.path() {
297− self.surface
266+ self.surface.set_string(
267+ 6,
268+ self.size.1 - 1,
298− .set_string(6, self.size.1 - 1, path.to_string_lossy(), text_color);
269+ path.to_string_lossy(),
270+ self.text_color,
271+ );
299 }
272 }
300
273
301 let cursor = view.state.selection().cursor();
274 let cursor = view.state.selection().cursor();
302
275
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;
281+ pos
308− }
282+ };
309
283
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));
311 }
285 }
286+}
287+
288+impl Application {
289+ pub fn new(mut args: Args) -> Result<Self, Error> {
290+ let terminal = Renderer::new()?;
291+ let mut editor = Editor::new();
292+
293+ if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
294+ editor.open(file, terminal.size)?;
295+ }
296+
297+ let mut app = Self {
298+ editor,
299+ terminal,
300+ // TODO; move to state
301+ prompt: None,
302+ should_close: false,
303+ };
304+
305+ Ok(app)
306+ }
307+
308+ pub fn set_prompt(&mut self) {
309+ // let commands = |input| match input {
310+ // "q" => self.should_close = true,
311+ // _ => (),
312+ // };
313+ // let prompt = Prompt::new(|input| None, commands);
314+ // self.prompt = Some(prompt);
315+ }
316+
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
319+
320+ if let Some(view) = &mut self.editor.view {
321+ self.terminal.render_view(view, viewport);
322+ }
323+
324+ if let Some(prompt) = &self.prompt {
325+ self.terminal.render_prompt(prompt);
326+ }
327+
328+ // TODO: drop unwrap
329+ self.terminal.render_cursor(
330+ self.editor.view.as_ref().unwrap(),
331+ self.prompt.as_ref(),
332+ viewport,
333+ );
334+ }
312
335
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);
332
352
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()
337 };
357 };
338
358
@@ -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();
358 }
378 }
359 Mode::Command => {
379 Mode::Command => {
360− self.prompt.unwrap().handle_input(event, view);
380+ self.prompt.as_mut().unwrap().handle_input(event, view);
361 }
381 }
362 mode => {
382 mode => {
363 if let Some(command) = keymap[&mode].get(&keys) {
383 if let Some(command) = keymap[&mode].get(&keys) {
helix-term/src/main.rs+3−3
@@ -1,8 +1,8 @@
1#![allow(unused)]
1#![allow(unused)]
2
2
3−mod editor;
3+mod application;
4
4
5−use editor::Editor;
5+use application::Application;
6
6
7use clap::{App, Arg};
7use clap::{App, Arg};
8use std::path::PathBuf;
8use std::path::PathBuf;
@@ -27,7 +27,7 @@ fn main() -> Result<(), Error> {
27 }
27 }
28
28
29 smol::block_on(EX.run(async {
29 smol::block_on(EX.run(async {
30− editor::Editor::new(args).unwrap().run().await;
30+ Application::new(args).unwrap().run().await;
31 }));
31 }));
32
32
33 Ok(())
33 Ok(())
helix-view/src/editor.rsnew file+20
@@ -0,0 +1,20 @@
1+use crate::View;
2+
3+use std::path::PathBuf;
4+
5+use anyhow::Error;
6+
7+pub struct Editor {
8+ pub view: Option<View>,
9+}
10+
11+impl Editor {
12+ pub fn new() -> Self {
13+ Self { view: None }
14+ }
15+
16+ pub fn open(&mut self, path: PathBuf, size: (u16, u16)) -> Result<(), Error> {
17+ self.view = Some(View::open(path, size)?);
18+ Ok(())
19+ }
20+}
helix-view/src/lib.rs+2
@@ -1,7 +1,9 @@
1pub mod commands;
1pub mod commands;
2+pub mod editor;
2pub mod keymap;
3pub mod keymap;
3pub mod prompt;
4pub mod prompt;
4pub mod theme;
5pub mod theme;
5pub mod view;
6pub mod view;
6
7
8+pub use editor::Editor;
7pub use view::View;
9pub use view::View;