Split parts of helix-term into helix-view.

It still largely depends on term for some types but I plan to change
that later.
pull/1/head
Blaž Hrastnik 4 years ago
parent 48330ddb5f
commit 935cfeae57

12
Cargo.lock generated

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

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

@ -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 - server-client architecture via gRPC, UI separate from core
- multi cursor based editing and slicing - multi cursor based editing and slicing
- WASM based plugins (builtin LSP & fuzzy file finder) - WASM based plugins (builtin LSP & fuzzy file finder)

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

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

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

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

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

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

@ -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}

@ -1,10 +1,10 @@
use helix_core::{ use helix_core::{
graphemes, graphemes,
state::{Direction, Granularity, Mode, State}, 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 /// 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). /// state (usually by creating and applying a transaction).

@ -1,9 +1,5 @@
use crate::commands::{self, Command}; use crate::commands::{self, Command};
use crossterm::{ use helix_core::{hashmap, state};
event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers},
execute,
style::Print,
};
use std::collections::HashMap; use std::collections::HashMap;
// Kakoune-inspired: // Kakoune-inspired:
@ -79,56 +75,57 @@ use std::collections::HashMap;
// } // }
// } // }
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!( hashmap!(
Key { state::Mode::Normal =>
code: KeyCode::Char('h'), hashmap!(
modifiers: Modifiers::NONE vec![Key {
} => commands::move_char_left as Command, code: KeyCode::Char('h'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Char('j'), }] => commands::move_char_left as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::move_line_down as Command, code: KeyCode::Char('j'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Char('k'), }] => commands::move_line_down as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::move_line_up as Command, code: KeyCode::Char('k'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Char('l'), }] => commands::move_line_up as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::move_char_right as Command, code: KeyCode::Char('l'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Char('i'), }] => commands::move_char_right as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::insert_mode as Command, code: KeyCode::Char('i'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Char('I'), }] => commands::insert_mode as Command,
modifiers: Modifiers::SHIFT, vec![Key {
} => commands::prepend_to_line as Command, code: KeyCode::Char('I'),
Key { modifiers: Modifiers::SHIFT,
code: KeyCode::Char('a'), }] => commands::prepend_to_line as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::append_mode as Command, code: KeyCode::Char('a'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Char('A'), }] => commands::append_mode as Command,
modifiers: Modifiers::SHIFT, vec![Key {
} => commands::append_to_line as Command, code: KeyCode::Char('A'),
Key { modifiers: Modifiers::SHIFT,
code: KeyCode::Char('o'), }] => commands::append_to_line as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::open_below as Command, code: KeyCode::Char('o'),
Key { modifiers: Modifiers::NONE
code: KeyCode::Esc, }] => commands::open_below as Command,
modifiers: Modifiers::NONE vec![Key {
} => commands::normal_mode as Command, code: KeyCode::Esc,
modifiers: Modifiers::NONE
}] => commands::normal_mode as Command,
)
) )
// hashmap!(
// Key {
// code: KeyCode::Esc,
// modifiers: Modifiers::NONE
// } => commands::normal_mode as Command,
// )
} }

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

@ -1,5 +1,86 @@
use helix_core::hashmap;
use std::collections::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. /// Color theme for syntax highlighting.
pub struct Theme { pub struct Theme {

@ -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);
}
}
}
Loading…
Cancel
Save