on master help sign in
Split parts of helix-term into helix-view.
935cfeaBlaž Hrastnik
Cargo.lock+11−1
@@ -410,12 +410,22 @@ dependencies = [
410 "argh",
410 "argh",
411 "crossterm",
411 "crossterm",
412 "helix-core",
412 "helix-core",
413− "helix-syntax",
413+ "helix-view",
414 "num_cpus",
414 "num_cpus",
415 "smol",
415 "smol",
416 "tui",
416 "tui",
417]
417]
418
418
419+[[package]]
420+name = "helix-view"
421+version = "0.1.0"
422+dependencies = [
423+ "anyhow",
424+ "crossterm",
425+ "helix-core",
426+ "tui",
427+]
428+
419[[package]]
429[[package]]
420name = "hermit-abi"
430name = "hermit-abi"
421version = "0.1.15"
431version = "0.1.15"
Cargo.toml+1
@@ -1,6 +1,7 @@
1[workspace]
1[workspace]
2members = [
2members = [
3 "helix-core",
3 "helix-core",
4+ "helix-view",
4 "helix-term",
5 "helix-term",
5 "helix-syntax",
6 "helix-syntax",
6]
7]
README.md+9
@@ -1,3 +1,12 @@
1+# Helix
2+
3+| Crate | Description |
4+| ----------- | ----------- |
5+| helix-core | Core editing primitives, functional. |
6+| helix-syntax | Tree-sitter grammars |
7+| helix-view | UI abstractions for use in backends, imperative shell. |
8+| helix-term | Terminal UI |
9+
1- server-client architecture via gRPC, UI separate from core
10- server-client architecture via gRPC, UI separate from core
2- multi cursor based editing and slicing
11- multi cursor based editing and slicing
3- WASM based plugins (builtin LSP & fuzzy file finder)
12- WASM based plugins (builtin LSP & fuzzy file finder)
helix-core/src/lib.rs+2
@@ -1,5 +1,7 @@
1#![allow(unused)]
1#![allow(unused)]
2+pub mod config;
2pub mod graphemes;
3pub mod graphemes;
4+pub mod macros;
3mod position;
5mod position;
4mod selection;
6mod selection;
5pub mod state;
7pub mod state;
helix-term/src/macros.rs → helix-core/src/macros.rsrenamed+1
@@ -1,3 +1,4 @@
1+#[macro_export]
1macro_rules! hashmap {
2macro_rules! hashmap {
2 (@single $($x:tt)*) => (());
3 (@single $($x:tt)*) => (());
3 (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
4 (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
helix-core/src/state.rs+1−1
@@ -4,7 +4,7 @@ use anyhow::Error;
4
4
5use std::path::PathBuf;
5use std::path::PathBuf;
6
6
7−#[derive(Copy, Clone)]
7+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
8pub enum Mode {
8pub enum Mode {
9 Normal,
9 Normal,
10 Insert,
10 Insert,
helix-term/Cargo.toml+2−2
@@ -12,12 +12,12 @@ path = "src/main.rs"
12
12
13[dependencies]
13[dependencies]
14helix-core = { path = "../helix-core" }
14helix-core = { path = "../helix-core" }
15−helix-syntax = { path = "../helix-syntax" }
15+helix-view = { path = "../helix-view", features = ["term"]}
16
16
17anyhow = "1"
17anyhow = "1"
18argh = "0.1.3"
18argh = "0.1.3"
19−crossterm = { version = "0.17", features = ["event-stream"] }
20
19
21smol = "1"
20smol = "1"
22num_cpus = "1.13.0"
21num_cpus = "1.13.0"
23tui = { version = "0.10.0", default-features = false, features = ["crossterm"] }
22tui = { version = "0.10.0", default-features = false, features = ["crossterm"] }
23+crossterm = { version = "0.17", features = ["event-stream"] }
helix-term/src/editor.rs+16−52
@@ -1,10 +1,11 @@
1−use crate::{commands, keymap, theme::Theme, Args};
1+use crate::Args;
2use helix_core::{
2use helix_core::{
3 state::coords_at_pos,
3 state::coords_at_pos,
4 state::Mode,
4 state::Mode,
5 syntax::{HighlightConfiguration, HighlightEvent, Highlighter},
5 syntax::{HighlightConfiguration, HighlightEvent, Highlighter},
6 State,
6 State,
7};
7};
8+use helix_view::{commands, keymap, View};
8
9
9use std::{
10use std::{
10 io::{self, stdout, Write},
11 io::{self, stdout, Write},
@@ -31,19 +32,12 @@ type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
31
32
32static EX: smol::Executor = smol::Executor::new();
33static EX: smol::Executor = smol::Executor::new();
33
34
34−pub struct View {
35− pub state: State,
36− pub first_line: u16,
37− pub size: (u16, u16),
38−}
39−
40pub struct Editor {
35pub struct Editor {
41 terminal: Terminal,
36 terminal: Terminal,
42 view: Option<View>,
37 view: Option<View>,
43 size: (u16, u16),
38 size: (u16, u16),
44 surface: Surface,
39 surface: Surface,
45 cache: Surface,
40 cache: Surface,
46− theme: Theme,
47}
41}
48
42
49impl Editor {
43impl Editor {
@@ -53,7 +47,6 @@ impl Editor {
53 let mut terminal = Terminal::new(backend)?;
47 let mut terminal = Terminal::new(backend)?;
54 let size = terminal::size().unwrap();
48 let size = terminal::size().unwrap();
55 let area = Rect::new(0, 0, size.0, size.1);
49 let area = Rect::new(0, 0, size.0, size.1);
56− let theme = Theme::default();
57
50
58 let mut editor = Editor {
51 let mut editor = Editor {
59 terminal,
52 terminal,
@@ -61,7 +54,6 @@ impl Editor {
61 size,
54 size,
62 surface: Surface::empty(area),
55 surface: Surface::empty(area),
63 cache: Surface::empty(area),
56 cache: Surface::empty(area),
64− theme,
65 // TODO; move to state
57 // TODO; move to state
66 };
58 };
67
59
@@ -73,20 +65,7 @@ impl Editor {
73 }
65 }
74
66
75 pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
67 pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
76− let mut state = State::load(path)?;
68+ self.view = Some(View::open(path, self.size)?);
77− state
78− .syntax
79− .as_mut()
80− .unwrap()
81− .configure(self.theme.scopes());
82−
83− let view = View {
84− state,
85− first_line: 0,
86− size: self.size,
87− };
88−
89− self.view = Some(view);
90 Ok(())
69 Ok(())
91 }
70 }
92
71
@@ -102,7 +81,7 @@ impl Editor {
102
81
103 // clear with background color
82 // clear with background color
104 self.surface
83 self.surface
105− .set_style(area, self.theme.get("ui.background"));
84+ .set_style(area, view.theme.get("ui.background").into());
106
85
107 let offset = 5 + 1; // 5 linenr + 1 gutter
86 let offset = 5 + 1; // 5 linenr + 1 gutter
108 let viewport = Rect::new(offset, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
87 let viewport = Rect::new(offset, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
@@ -161,7 +140,9 @@ impl Editor {
161 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
140 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
162
141
163 let style = match spans.first() {
142 let style = match spans.first() {
143+ Some(span) => {
164− Some(span) => self.theme.get(self.theme.scopes()[span.0].as_str()),
144+ view.theme.get(view.theme.scopes()[span.0].as_str()).into()
145+ }
165 None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
146 None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
166 };
147 };
167
148
@@ -202,7 +183,7 @@ impl Editor {
202 }
183 }
203
184
204 let mut line = 0;
185 let mut line = 0;
205− let style = self.theme.get("ui.linenr");
186+ let style: Style = view.theme.get("ui.linenr").into();
206 for i in view.first_line..(last_line as u16) {
187 for i in view.first_line..(last_line as u16) {
207 self.surface
188 self.surface
208 .set_stringn(0, line, format!("{:>5}", i + 1), 5, style); // lavender
189 .set_stringn(0, line, format!("{:>5}", i + 1), 5, style); // lavender
@@ -241,7 +222,7 @@ impl Editor {
241 };
222 };
242 self.surface.set_style(
223 self.surface.set_style(
243 Rect::new(0, self.size.1 - 1, self.size.0, 1),
224 Rect::new(0, self.size.1 - 1, self.size.0, 1),
244− self.theme.get("ui.statusline"),
225+ view.theme.get("ui.statusline").into(),
245 );
226 );
246 // TODO: unfocused one with different color
227 // TODO: unfocused one with different color
247 let text_color = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
228 let text_color = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
@@ -296,7 +277,9 @@ impl Editor {
296 self.cache = Surface::empty(area);
277 self.cache = Surface::empty(area);
297
278
298 // TODO: simplistic ensure cursor in view for now
279 // TODO: simplistic ensure cursor in view for now
280+ if let Some(view) = &mut self.view {
299− self.ensure_cursor_in_view();
281+ view.ensure_cursor_in_view()
282+ };
300
283
301 self.render();
284 self.render();
302 }
285 }
@@ -333,18 +316,19 @@ impl Editor {
333 _ => (), // skip
316 _ => (), // skip
334 }
317 }
335 // TODO: simplistic ensure cursor in view for now
318 // TODO: simplistic ensure cursor in view for now
336− self.ensure_cursor_in_view();
319+ view.ensure_cursor_in_view();
337
320
338 self.render();
321 self.render();
339 }
322 }
340 Mode::Normal => {
323 Mode::Normal => {
341 // TODO: handle modes and sequences (`gg`)
324 // TODO: handle modes and sequences (`gg`)
325+ let keys = vec![event];
342− if let Some(command) = keymap.get(&event) {
326+ if let Some(command) = keymap[&Mode::Normal].get(&keys) {
343 // TODO: handle count other than 1
327 // TODO: handle count other than 1
344 command(view, 1);
328 command(view, 1);
345
329
346 // TODO: simplistic ensure cursor in view for now
330 // TODO: simplistic ensure cursor in view for now
347− self.ensure_cursor_in_view();
331+ view.ensure_cursor_in_view();
348
332
349 self.render();
333 self.render();
350 }
334 }
@@ -361,26 +345,6 @@ impl Editor {
361 }
345 }
362 }
346 }
363
347
364− fn ensure_cursor_in_view(&mut self) {
365− if let Some(view) = &mut self.view {
366− let cursor = view.state.selection().cursor();
367− let line = view.state.doc().char_to_line(cursor) as u16;
368− let document_end = view.first_line + self.size.1.saturating_sub(1) - 1;
369−
370− let padding = 5u16;
371−
372− // TODO: side scroll
373−
374− if line > document_end.saturating_sub(padding) {
375− // scroll down
376− view.first_line += line - (document_end.saturating_sub(padding));
377− } else if line < view.first_line + padding {
378− // scroll up
379− view.first_line = line.saturating_sub(padding);
380− }
381− }
382− }
383−
384 pub async fn run(&mut self) -> Result<(), Error> {
348 pub async fn run(&mut self) -> Result<(), Error> {
385 enable_raw_mode()?;
349 enable_raw_mode()?;
386
350
helix-term/src/main.rs−5
@@ -1,11 +1,6 @@
1#![allow(unused)]
1#![allow(unused)]
2−#[macro_use]
3−mod macros;
4
2
5−mod commands;
6mod editor;
3mod editor;
7−mod keymap;
8−mod theme;
9
4
10use editor::Editor;
5use editor::Editor;
11
6
helix-view/Cargo.tomlnew file+18
@@ -0,0 +1,18 @@
1+[package]
2+name = "helix-view"
3+version = "0.1.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2018"
6+
7+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+[features]
10+term = ["tui", "crossterm"]
11+
12+[dependencies]
13+anyhow = "1.0.32"
14+helix-core = { path = "../helix-core" }
15+
16+# Conversion traits
17+tui = { version = "0.10.0", default-features = false, features = ["crossterm"], optional = true}
18+crossterm = { version = "0.17", features = ["event-stream"], optional = true}
helix-term/src/commands.rs → helix-view/src/commands.rsrenamed+2−2
@@ -1,10 +1,10 @@
1use helix_core::{
1use helix_core::{
2 graphemes,
2 graphemes,
3 state::{Direction, Granularity, Mode, State},
3 state::{Direction, Granularity, Mode, State},
4− ChangeSet, Range, Selection, Tendril, Transaction,
4+ Range, Selection, Tendril, Transaction,
5};
5};
6
6
7−use crate::editor::View;
7+use crate::view::View;
8
8
9/// A command is a function that takes the current state and a count, and does a side-effect on the
9/// A command is a function that takes the current state and a count, and does a side-effect on the
10/// state (usually by creating and applying a transaction).
10/// state (usually by creating and applying a transaction).
helix-term/src/keymap.rs → helix-view/src/keymap.rsrenamed+51−54
@@ -1,9 +1,5 @@
1use crate::commands::{self, Command};
1use crate::commands::{self, Command};
2−use crossterm::{
2+use helix_core::{hashmap, state};
3− event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers},
4− execute,
5− style::Print,
6−};
7use std::collections::HashMap;
3use std::collections::HashMap;
8
4
9// Kakoune-inspired:
5// Kakoune-inspired:
@@ -79,56 +75,57 @@ use std::collections::HashMap;
79// }
75// }
80// }
76// }
81
77
82−type Keymap = HashMap<Key, Command>;
78+#[cfg(feature = "term")]
79+pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
83
80
81+// TODO: could be trie based
82+type Keymap = HashMap<Vec<Key>, Command>;
83+type Keymaps = HashMap<state::Mode, Keymap>;
84+
84−pub fn default() -> Keymap {
85+pub fn default() -> Keymaps {
85 hashmap!(
86 hashmap!(
87+ state::Mode::Normal =>
88+ hashmap!(
86− Key {
89+ vec![Key {
87− code: KeyCode::Char('h'),
90+ code: KeyCode::Char('h'),
88− modifiers: Modifiers::NONE
91+ modifiers: Modifiers::NONE
89− } => commands::move_char_left as Command,
92+ }] => commands::move_char_left as Command,
90− Key {
93+ vec![Key {
91− code: KeyCode::Char('j'),
94+ code: KeyCode::Char('j'),
92− modifiers: Modifiers::NONE
95+ modifiers: Modifiers::NONE
93− } => commands::move_line_down as Command,
96+ }] => commands::move_line_down as Command,
94− Key {
97+ vec![Key {
95− code: KeyCode::Char('k'),
98+ code: KeyCode::Char('k'),
96− modifiers: Modifiers::NONE
99+ modifiers: Modifiers::NONE
97− } => commands::move_line_up as Command,
100+ }] => commands::move_line_up as Command,
98− Key {
101+ vec![Key {
99− code: KeyCode::Char('l'),
102+ code: KeyCode::Char('l'),
100− modifiers: Modifiers::NONE
103+ modifiers: Modifiers::NONE
101− } => commands::move_char_right as Command,
104+ }] => commands::move_char_right as Command,
102− Key {
105+ vec![Key {
103− code: KeyCode::Char('i'),
106+ code: KeyCode::Char('i'),
104− modifiers: Modifiers::NONE
107+ modifiers: Modifiers::NONE
105− } => commands::insert_mode as Command,
108+ }] => commands::insert_mode as Command,
106− Key {
109+ vec![Key {
107− code: KeyCode::Char('I'),
110+ code: KeyCode::Char('I'),
108− modifiers: Modifiers::SHIFT,
111+ modifiers: Modifiers::SHIFT,
109− } => commands::prepend_to_line as Command,
112+ }] => commands::prepend_to_line as Command,
110− Key {
113+ vec![Key {
111− code: KeyCode::Char('a'),
114+ code: KeyCode::Char('a'),
112− modifiers: Modifiers::NONE
115+ modifiers: Modifiers::NONE
113− } => commands::append_mode as Command,
116+ }] => commands::append_mode as Command,
114− Key {
117+ vec![Key {
115− code: KeyCode::Char('A'),
118+ code: KeyCode::Char('A'),
116− modifiers: Modifiers::SHIFT,
119+ modifiers: Modifiers::SHIFT,
117− } => commands::append_to_line as Command,
120+ }] => commands::append_to_line as Command,
118− Key {
121+ vec![Key {
119− code: KeyCode::Char('o'),
122+ code: KeyCode::Char('o'),
120− modifiers: Modifiers::NONE
123+ modifiers: Modifiers::NONE
121− } => commands::open_below as Command,
124+ }] => commands::open_below as Command,
122− Key {
125+ vec![Key {
123− code: KeyCode::Esc,
126+ code: KeyCode::Esc,
124− modifiers: Modifiers::NONE
127+ modifiers: Modifiers::NONE
125− } => commands::normal_mode as Command,
128+ }] => commands::normal_mode as Command,
129+ )
126 )
130 )
127−
128− // hashmap!(
129− // Key {
130− // code: KeyCode::Esc,
131− // modifiers: Modifiers::NONE
132− // } => commands::normal_mode as Command,
133− // )
134}
131}
helix-view/src/lib.rsnew file+6
@@ -0,0 +1,6 @@
1+pub mod commands;
2+pub mod keymap;
3+pub mod theme;
4+pub mod view;
5+
6+pub use view::View;
helix-term/src/theme.rs → helix-view/src/theme.rsrenamed+82−1
@@ -1,5 +1,86 @@
1+use helix_core::hashmap;
1use std::collections::HashMap;
2use std::collections::HashMap;
3+
4+#[cfg(feature = "term")]
2−use tui::style::{Color, Style};
5+pub use tui::style::{Color, Style};
6+
7+// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
8+// pub struct Color {
9+// pub r: u8,
10+// pub g: u8,
11+// pub b: u8,
12+// }
13+
14+// impl Color {
15+// pub fn new(r: u8, g: u8, b: u8) -> Self {
16+// Self { r, g, b }
17+// }
18+// }
19+
20+// #[cfg(feature = "term")]
21+// impl Into<tui::style::Color> for Color {
22+// fn into(self) -> tui::style::Color {
23+// tui::style::Color::Rgb(self.r, self.g, self.b)
24+// }
25+// }
26+
27+// impl std::str::FromStr for Color {
28+// type Err = ();
29+
30+// /// Tries to parse a string (`'#FFFFFF'` or `'FFFFFF'`) into RGB.
31+// fn from_str(input: &str) -> Result<Self, Self::Err> {
32+// let input = input.trim();
33+// let input = match (input.chars().next(), input.len()) {
34+// (Some('#'), 7) => &input[1..],
35+// (_, 6) => input,
36+// _ => return Err(()),
37+// };
38+
39+// u32::from_str_radix(&input, 16)
40+// .map(|s| Color {
41+// r: ((s >> 16) & 0xFF) as u8,
42+// g: ((s >> 8) & 0xFF) as u8,
43+// b: (s & 0xFF) as u8,
44+// })
45+// .map_err(|_| ())
46+// }
47+// }
48+
49+// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
50+// pub struct Style {
51+// pub fg: Option<Color>,
52+// pub bg: Option<Color>,
53+// // TODO: modifiers (bold, underline, italic, etc)
54+// }
55+
56+// impl Style {
57+// pub fn fg(mut self, fg: Color) -> Self {
58+// self.fg = Some(fg);
59+// self
60+// }
61+
62+// pub fn bg(mut self, bg: Color) -> Self {
63+// self.bg = Some(bg);
64+// self
65+// }
66+// }
67+
68+// #[cfg(feature = "term")]
69+// impl Into<tui::style::Style> for Style {
70+// fn into(self) -> tui::style::Style {
71+// let style = tui::style::Style::default();
72+
73+// if let Some(fg) = self.fg {
74+// style.fg(fg.into());
75+// }
76+
77+// if let Some(bg) = self.bg {
78+// style.bg(bg.into());
79+// }
80+
81+// style
82+// }
83+// }
3
84
4/// Color theme for syntax highlighting.
85/// Color theme for syntax highlighting.
5pub struct Theme {
86pub struct Theme {
helix-view/src/view.rsnew file+48
@@ -0,0 +1,48 @@
1+use anyhow::Error;
2+
3+use std::path::PathBuf;
4+
5+use crate::theme::Theme;
6+use helix_core::State;
7+
8+pub struct View {
9+ pub state: State,
10+ pub first_line: u16,
11+ pub size: (u16, u16),
12+ pub theme: Theme, // TODO: share one instance
13+}
14+
15+impl View {
16+ pub fn open(path: PathBuf, size: (u16, u16)) -> Result<View, Error> {
17+ let mut state = State::load(path)?;
18+ let theme = Theme::default();
19+ state.syntax.as_mut().unwrap().configure(theme.scopes());
20+
21+ let view = View {
22+ state,
23+ first_line: 0,
24+ size, // TODO: pass in from term
25+ theme,
26+ };
27+
28+ Ok(view)
29+ }
30+
31+ pub fn ensure_cursor_in_view(&mut self) {
32+ let cursor = self.state.selection().cursor();
33+ let line = self.state.doc().char_to_line(cursor) as u16;
34+ let document_end = self.first_line + self.size.1.saturating_sub(1) - 1;
35+
36+ let padding = 5u16;
37+
38+ // TODO: side scroll
39+
40+ if line > document_end.saturating_sub(padding) {
41+ // scroll down
42+ self.first_line += line - (document_end.saturating_sub(padding));
43+ } else if line < self.first_line + padding {
44+ // scroll up
45+ self.first_line = line.saturating_sub(padding);
46+ }
47+ }
48+}