Split parts of helix-term into helix-view.

It still largely depends on term for some types but I plan to change
that later.
This commit is contained in:
Blaž Hrastnik 2020-09-21 18:24:16 +09:00
parent 48330ddb5f
commit 935cfeae57
15 changed files with 250 additions and 118 deletions

12
Cargo.lock generated
View File

@ -410,12 +410,22 @@ dependencies = [
"argh",
"crossterm",
"helix-core",
"helix-syntax",
"helix-view",
"num_cpus",
"smol",
"tui",
]
[[package]]
name = "helix-view"
version = "0.1.0"
dependencies = [
"anyhow",
"crossterm",
"helix-core",
"tui",
]
[[package]]
name = "hermit-abi"
version = "0.1.15"

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"helix-core",
"helix-view",
"helix-term",
"helix-syntax",
]

View File

@ -1,3 +1,12 @@
# Helix
| Crate | Description |
| ----------- | ----------- |
| helix-core | Core editing primitives, functional. |
| helix-syntax | Tree-sitter grammars |
| helix-view | UI abstractions for use in backends, imperative shell. |
| helix-term | Terminal UI |
- server-client architecture via gRPC, UI separate from core
- multi cursor based editing and slicing
- WASM based plugins (builtin LSP & fuzzy file finder)

View File

@ -1,5 +1,7 @@
#![allow(unused)]
pub mod config;
pub mod graphemes;
pub mod macros;
mod position;
mod selection;
pub mod state;

View File

@ -1,3 +1,4 @@
#[macro_export]
macro_rules! hashmap {
(@single $($x:tt)*) => (());
(@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));

View File

@ -4,7 +4,7 @@
use std::path::PathBuf;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mode {
Normal,
Insert,

View File

@ -12,12 +12,12 @@ path = "src/main.rs"
[dependencies]
helix-core = { path = "../helix-core" }
helix-syntax = { path = "../helix-syntax" }
helix-view = { path = "../helix-view", features = ["term"]}
anyhow = "1"
argh = "0.1.3"
crossterm = { version = "0.17", features = ["event-stream"] }
smol = "1"
num_cpus = "1.13.0"
tui = { version = "0.10.0", default-features = false, features = ["crossterm"] }
crossterm = { version = "0.17", features = ["event-stream"] }

View File

@ -1,10 +1,11 @@
use crate::{commands, keymap, theme::Theme, Args};
use crate::Args;
use helix_core::{
state::coords_at_pos,
state::Mode,
syntax::{HighlightConfiguration, HighlightEvent, Highlighter},
State,
};
use helix_view::{commands, keymap, View};
use std::{
io::{self, stdout, Write},
@ -31,19 +32,12 @@
static EX: smol::Executor = smol::Executor::new();
pub struct View {
pub state: State,
pub first_line: u16,
pub size: (u16, u16),
}
pub struct Editor {
terminal: Terminal,
view: Option<View>,
size: (u16, u16),
surface: Surface,
cache: Surface,
theme: Theme,
}
impl Editor {
@ -53,7 +47,6 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
let mut terminal = Terminal::new(backend)?;
let size = terminal::size().unwrap();
let area = Rect::new(0, 0, size.0, size.1);
let theme = Theme::default();
let mut editor = Editor {
terminal,
@ -61,7 +54,6 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
size,
surface: Surface::empty(area),
cache: Surface::empty(area),
theme,
// TODO; move to state
};
@ -73,20 +65,7 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
}
pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
let mut state = State::load(path)?;
state
.syntax
.as_mut()
.unwrap()
.configure(self.theme.scopes());
let view = View {
state,
first_line: 0,
size: self.size,
};
self.view = Some(view);
self.view = Some(View::open(path, self.size)?);
Ok(())
}
@ -102,7 +81,7 @@ fn render(&mut self) {
// clear with background color
self.surface
.set_style(area, self.theme.get("ui.background"));
.set_style(area, view.theme.get("ui.background").into());
let offset = 5 + 1; // 5 linenr + 1 gutter
let viewport = Rect::new(offset, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
@ -161,7 +140,9 @@ fn render(&mut self) {
use helix_core::graphemes::{grapheme_width, RopeGraphemes};
let style = match spans.first() {
Some(span) => self.theme.get(self.theme.scopes()[span.0].as_str()),
Some(span) => {
view.theme.get(view.theme.scopes()[span.0].as_str()).into()
}
None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
};
@ -202,7 +183,7 @@ fn render(&mut self) {
}
let mut line = 0;
let style = self.theme.get("ui.linenr");
let style: Style = view.theme.get("ui.linenr").into();
for i in view.first_line..(last_line as u16) {
self.surface
.set_stringn(0, line, format!("{:>5}", i + 1), 5, style); // lavender
@ -241,7 +222,7 @@ fn render(&mut self) {
};
self.surface.set_style(
Rect::new(0, self.size.1 - 1, self.size.0, 1),
self.theme.get("ui.statusline"),
view.theme.get("ui.statusline").into(),
);
// TODO: unfocused one with different color
let text_color = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
@ -296,7 +277,9 @@ pub async fn event_loop(&mut self) {
self.cache = Surface::empty(area);
// TODO: simplistic ensure cursor in view for now
self.ensure_cursor_in_view();
if let Some(view) = &mut self.view {
view.ensure_cursor_in_view()
};
self.render();
}
@ -333,18 +316,19 @@ pub async fn event_loop(&mut self) {
_ => (), // skip
}
// TODO: simplistic ensure cursor in view for now
self.ensure_cursor_in_view();
view.ensure_cursor_in_view();
self.render();
}
Mode::Normal => {
// TODO: handle modes and sequences (`gg`)
if let Some(command) = keymap.get(&event) {
let keys = vec![event];
if let Some(command) = keymap[&Mode::Normal].get(&keys) {
// TODO: handle count other than 1
command(view, 1);
// TODO: simplistic ensure cursor in view for now
self.ensure_cursor_in_view();
view.ensure_cursor_in_view();
self.render();
}
@ -361,26 +345,6 @@ pub async fn event_loop(&mut self) {
}
}
fn ensure_cursor_in_view(&mut self) {
if let Some(view) = &mut self.view {
let cursor = view.state.selection().cursor();
let line = view.state.doc().char_to_line(cursor) as u16;
let document_end = view.first_line + self.size.1.saturating_sub(1) - 1;
let padding = 5u16;
// TODO: side scroll
if line > document_end.saturating_sub(padding) {
// scroll down
view.first_line += line - (document_end.saturating_sub(padding));
} else if line < view.first_line + padding {
// scroll up
view.first_line = line.saturating_sub(padding);
}
}
}
pub async fn run(&mut self) -> Result<(), Error> {
enable_raw_mode()?;

View File

@ -1,11 +1,6 @@
#![allow(unused)]
#[macro_use]
mod macros;
mod commands;
mod editor;
mod keymap;
mod theme;
use editor::Editor;

18
helix-view/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "helix-view"
version = "0.1.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
term = ["tui", "crossterm"]
[dependencies]
anyhow = "1.0.32"
helix-core = { path = "../helix-core" }
# Conversion traits
tui = { version = "0.10.0", default-features = false, features = ["crossterm"], optional = true}
crossterm = { version = "0.17", features = ["event-stream"], optional = true}

View File

@ -1,10 +1,10 @@
use helix_core::{
graphemes,
state::{Direction, Granularity, Mode, State},
ChangeSet, Range, Selection, Tendril, Transaction,
Range, Selection, Tendril, Transaction,
};
use crate::editor::View;
use crate::view::View;
/// A command is a function that takes the current state and a count, and does a side-effect on the
/// state (usually by creating and applying a transaction).

View File

@ -1,9 +1,5 @@
use crate::commands::{self, Command};
use crossterm::{
event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers},
execute,
style::Print,
};
use helix_core::{hashmap, state};
use std::collections::HashMap;
// Kakoune-inspired:
@ -79,56 +75,57 @@
// }
// }
type Keymap = HashMap<Key, Command>;
#[cfg(feature = "term")]
pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
pub fn default() -> Keymap {
// TODO: could be trie based
type Keymap = HashMap<Vec<Key>, Command>;
type Keymaps = HashMap<state::Mode, Keymap>;
pub fn default() -> Keymaps {
hashmap!(
Key {
code: KeyCode::Char('h'),
modifiers: Modifiers::NONE
} => commands::move_char_left as Command,
Key {
code: KeyCode::Char('j'),
modifiers: Modifiers::NONE
} => commands::move_line_down as Command,
Key {
code: KeyCode::Char('k'),
modifiers: Modifiers::NONE
} => commands::move_line_up as Command,
Key {
code: KeyCode::Char('l'),
modifiers: Modifiers::NONE
} => commands::move_char_right as Command,
Key {
code: KeyCode::Char('i'),
modifiers: Modifiers::NONE
} => commands::insert_mode as Command,
Key {
code: KeyCode::Char('I'),
modifiers: Modifiers::SHIFT,
} => commands::prepend_to_line as Command,
Key {
code: KeyCode::Char('a'),
modifiers: Modifiers::NONE
} => commands::append_mode as Command,
Key {
code: KeyCode::Char('A'),
modifiers: Modifiers::SHIFT,
} => commands::append_to_line as Command,
Key {
code: KeyCode::Char('o'),
modifiers: Modifiers::NONE
} => commands::open_below as Command,
Key {
code: KeyCode::Esc,
modifiers: Modifiers::NONE
} => commands::normal_mode as Command,
state::Mode::Normal =>
hashmap!(
vec![Key {
code: KeyCode::Char('h'),
modifiers: Modifiers::NONE
}] => commands::move_char_left as Command,
vec![Key {
code: KeyCode::Char('j'),
modifiers: Modifiers::NONE
}] => commands::move_line_down as Command,
vec![Key {
code: KeyCode::Char('k'),
modifiers: Modifiers::NONE
}] => commands::move_line_up as Command,
vec![Key {
code: KeyCode::Char('l'),
modifiers: Modifiers::NONE
}] => commands::move_char_right as Command,
vec![Key {
code: KeyCode::Char('i'),
modifiers: Modifiers::NONE
}] => commands::insert_mode as Command,
vec![Key {
code: KeyCode::Char('I'),
modifiers: Modifiers::SHIFT,
}] => commands::prepend_to_line as Command,
vec![Key {
code: KeyCode::Char('a'),
modifiers: Modifiers::NONE
}] => commands::append_mode as Command,
vec![Key {
code: KeyCode::Char('A'),
modifiers: Modifiers::SHIFT,
}] => commands::append_to_line as Command,
vec![Key {
code: KeyCode::Char('o'),
modifiers: Modifiers::NONE
}] => commands::open_below as Command,
vec![Key {
code: KeyCode::Esc,
modifiers: Modifiers::NONE
}] => commands::normal_mode as Command,
)
)
// hashmap!(
// Key {
// code: KeyCode::Esc,
// modifiers: Modifiers::NONE
// } => commands::normal_mode as Command,
// )
}

6
helix-view/src/lib.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod commands;
pub mod keymap;
pub mod theme;
pub mod view;
pub use view::View;

View File

@ -1,5 +1,86 @@
use helix_core::hashmap;
use std::collections::HashMap;
use tui::style::{Color, Style};
#[cfg(feature = "term")]
pub use tui::style::{Color, Style};
// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
// pub struct Color {
// pub r: u8,
// pub g: u8,
// pub b: u8,
// }
// impl Color {
// pub fn new(r: u8, g: u8, b: u8) -> Self {
// Self { r, g, b }
// }
// }
// #[cfg(feature = "term")]
// impl Into<tui::style::Color> for Color {
// fn into(self) -> tui::style::Color {
// tui::style::Color::Rgb(self.r, self.g, self.b)
// }
// }
// impl std::str::FromStr for Color {
// type Err = ();
// /// Tries to parse a string (`'#FFFFFF'` or `'FFFFFF'`) into RGB.
// fn from_str(input: &str) -> Result<Self, Self::Err> {
// let input = input.trim();
// let input = match (input.chars().next(), input.len()) {
// (Some('#'), 7) => &input[1..],
// (_, 6) => input,
// _ => return Err(()),
// };
// u32::from_str_radix(&input, 16)
// .map(|s| Color {
// r: ((s >> 16) & 0xFF) as u8,
// g: ((s >> 8) & 0xFF) as u8,
// b: (s & 0xFF) as u8,
// })
// .map_err(|_| ())
// }
// }
// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
// pub struct Style {
// pub fg: Option<Color>,
// pub bg: Option<Color>,
// // TODO: modifiers (bold, underline, italic, etc)
// }
// impl Style {
// pub fn fg(mut self, fg: Color) -> Self {
// self.fg = Some(fg);
// self
// }
// pub fn bg(mut self, bg: Color) -> Self {
// self.bg = Some(bg);
// self
// }
// }
// #[cfg(feature = "term")]
// impl Into<tui::style::Style> for Style {
// fn into(self) -> tui::style::Style {
// let style = tui::style::Style::default();
// if let Some(fg) = self.fg {
// style.fg(fg.into());
// }
// if let Some(bg) = self.bg {
// style.bg(bg.into());
// }
// style
// }
// }
/// Color theme for syntax highlighting.
pub struct Theme {

48
helix-view/src/view.rs Normal file
View File

@ -0,0 +1,48 @@
use anyhow::Error;
use std::path::PathBuf;
use crate::theme::Theme;
use helix_core::State;
pub struct View {
pub state: State,
pub first_line: u16,
pub size: (u16, u16),
pub theme: Theme, // TODO: share one instance
}
impl View {
pub fn open(path: PathBuf, size: (u16, u16)) -> Result<View, Error> {
let mut state = State::load(path)?;
let theme = Theme::default();
state.syntax.as_mut().unwrap().configure(theme.scopes());
let view = View {
state,
first_line: 0,
size, // TODO: pass in from term
theme,
};
Ok(view)
}
pub fn ensure_cursor_in_view(&mut self) {
let cursor = self.state.selection().cursor();
let line = self.state.doc().char_to_line(cursor) as u16;
let document_end = self.first_line + self.size.1.saturating_sub(1) - 1;
let padding = 5u16;
// TODO: side scroll
if line > document_end.saturating_sub(padding) {
// scroll down
self.first_line += line - (document_end.saturating_sub(padding));
} else if line < self.first_line + padding {
// scroll up
self.first_line = line.saturating_sub(padding);
}
}
}