aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
authorBlaž Hrastnik2020-05-25 04:02:21 +0000
committerBlaž Hrastnik2020-05-25 04:02:21 +0000
commit44ff4d3c1f5da05e57ce99ba9d67b80a334def83 (patch)
tree232b8eebab7f709eaf84b8649791a6c74448bfdb /helix-term
parent240e5f4e3d27415b792776dd126d15302d53e83b (diff)
Implement a new core based on CodeMirror.
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/Cargo.toml5
-rw-r--r--helix-term/src/editor.rs281
-rw-r--r--helix-term/src/main.rs229
3 files changed, 298 insertions, 217 deletions
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index f6528034..ea4d5b55 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -15,5 +15,8 @@ path = "src/main.rs"
# path = "src/line.rs"
[dependencies]
-termwiz = { git = "https://github.com/wez/wezterm", features = ["widgets"] }
+# termwiz = { git = "https://github.com/wez/wezterm", features = ["widgets"] }
+termwiz = { path = "../../wezterm/termwiz", default-features = false, features = ["widgets"] }
anyhow = "1.0.31"
+argh = "0.1.3"
+helix-core = { path = "../helix-core" }
diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs
new file mode 100644
index 00000000..54c70e1b
--- /dev/null
+++ b/helix-term/src/editor.rs
@@ -0,0 +1,281 @@
+#![allow(unused)]
+use anyhow::Error;
+use termwiz::caps::Capabilities;
+use termwiz::cell::AttributeChange;
+use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
+use termwiz::input::*;
+use termwiz::surface::Change;
+use termwiz::terminal::{buffered::BufferedTerminal, SystemTerminal, Terminal};
+use termwiz::widgets::*;
+
+use crate::Args;
+
+use std::{env, path::PathBuf};
+
+use helix_core::Buffer;
+
+/// This is a widget for our application
+pub struct MainScreen {}
+
+impl MainScreen {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl Widget for MainScreen {
+ fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
+ true // handled it all
+ }
+
+ /// Draw ourselves into the surface provided by RenderArgs
+ fn render(&mut self, args: &mut RenderArgs) {
+ // args.surface.add_change(Change::ClearScreen(
+ // ColorAttribute::TrueColorWithPaletteFallback(
+ // RgbColor::new(0x31, 0x1B, 0x92),
+ // AnsiColor::Black.into(),
+ // ),
+ // ));
+ // args.surface
+ // .add_change(Change::Attribute(AttributeChange::Foreground(
+ // ColorAttribute::TrueColorWithPaletteFallback(
+ // RgbColor::new(0xB3, 0x88, 0xFF),
+ // AnsiColor::Purple.into(),
+ // ),
+ // )));
+ }
+
+ fn get_size_constraints(&self) -> layout::Constraints {
+ let mut constraints = layout::Constraints::default();
+ constraints.child_orientation = layout::ChildOrientation::Vertical;
+ constraints
+ }
+}
+
+pub struct BufferComponent<'a> {
+ text: String,
+ buffer: &'a mut Buffer,
+
+ first_line: usize,
+}
+
+impl<'a> BufferComponent<'a> {
+ /// Initialize the widget with the input text
+ pub fn new(buffer: &'a mut Buffer) -> Self {
+ Self {
+ buffer,
+ text: String::new(),
+
+ first_line: 0,
+ }
+ }
+}
+
+impl<'a> Widget for BufferComponent<'a> {
+ fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
+ match event {
+ WidgetEvent::Input(InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('k'),
+ ..
+ })) => {
+ self.first_line = self.first_line.saturating_sub(1);
+ }
+ WidgetEvent::Input(InputEvent::Key(KeyEvent {
+ key: KeyCode::Char('j'),
+ ..
+ })) => {
+ self.first_line = self.first_line.saturating_add(1);
+ }
+ WidgetEvent::Input(InputEvent::Key(KeyEvent {
+ key: KeyCode::Enter,
+ ..
+ })) => {
+ self.text.push_str("\r\n");
+ }
+ WidgetEvent::Input(InputEvent::Paste(s)) => {
+ self.text.push_str(&s);
+ }
+ _ => {}
+ }
+
+ true // handled it all
+ }
+
+ /// Draw ourselves into the surface provided by RenderArgs
+ fn render(&mut self, args: &mut RenderArgs) {
+ args.surface
+ .add_change(Change::ClearScreen(ColorAttribute::Default));
+
+ // args.surface
+ // .add_change(Change::Attribute(AttributeChange::Foreground(
+ // ColorAttribute::TrueColorWithPaletteFallback(
+ // RgbColor::new(0x11, 0x00, 0xFF),
+ // AnsiColor::Purple.into(),
+ // ),
+ // )));
+ let (_width, height) = args.surface.dimensions();
+
+ for line in self.buffer.contents.lines_at(self.first_line).take(height) {
+ args.surface
+ .add_change(unsafe { String::from_utf8_unchecked(line.bytes().collect()) });
+ args.surface.add_change("\r");
+ }
+ // args.surface
+ // .add_change(format!("🤷 surface size is {:?}\r\n", dims));
+ // args.surface.add_change(self.text.clone());
+
+ // Place the cursor at the end of the text.
+ // A more advanced text editing widget would manage the
+ // cursor position differently.
+ *args.cursor = CursorShapeAndPosition {
+ coords: args.surface.cursor_position().into(),
+ shape: termwiz::surface::CursorShape::SteadyBar,
+ ..Default::default()
+ };
+ }
+
+ fn get_size_constraints(&self) -> layout::Constraints {
+ let mut c = layout::Constraints::default();
+ c.set_valign(layout::VerticalAlignment::Top);
+ c
+ }
+}
+
+pub struct StatusLine {}
+
+impl StatusLine {
+ pub fn new() -> Self {
+ StatusLine {}
+ }
+}
+impl Widget for StatusLine {
+ fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
+ true
+ }
+
+ fn render(&mut self, args: &mut RenderArgs) {
+ args.surface.add_change(Change::ClearScreen(
+ ColorAttribute::TrueColorWithPaletteFallback(
+ RgbColor::new(0xFF, 0xFF, 0xFF),
+ AnsiColor::Black.into(),
+ ),
+ ));
+ args.surface
+ .add_change(Change::Attribute(AttributeChange::Foreground(
+ ColorAttribute::TrueColorWithPaletteFallback(
+ RgbColor::new(0x00, 0x00, 0x00),
+ AnsiColor::Black.into(),
+ ),
+ )));
+
+ args.surface.add_change(" helix");
+ }
+
+ fn get_size_constraints(&self) -> layout::Constraints {
+ *layout::Constraints::default()
+ .set_fixed_height(1)
+ .set_valign(layout::VerticalAlignment::Bottom)
+ }
+}
+
+pub struct Editor {
+ terminal: BufferedTerminal<SystemTerminal>,
+
+ buffer: Option<Buffer>,
+}
+
+impl Editor {
+ pub fn new(mut args: Args) -> Result<Self, Error> {
+ // Create a terminal
+ let caps = Capabilities::new_from_env()?;
+ let mut terminal = BufferedTerminal::new(SystemTerminal::new(caps)?)?;
+
+ let mut editor = Editor {
+ terminal,
+ buffer: None,
+ };
+
+ if let Some(file) = args.files.pop() {
+ editor.open(file)?;
+ }
+
+ Ok(editor)
+ }
+
+ pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
+ let buffer = Buffer::load(path)?;
+ self.buffer = Some(buffer);
+ Ok(())
+ }
+
+ pub fn run(&mut self) -> Result<(), Error> {
+ // Start with an empty string; typing into the app will
+ // update this string.
+ let mut typed_text = String::new();
+
+ {
+ let buf = &mut self.terminal;
+ // Put the terminal in raw mode + alternate screen
+ buf.terminal().enter_alternate_screen()?;
+ buf.terminal().set_raw_mode()?;
+
+ // Set up the UI
+ let mut ui = Ui::new();
+
+ let root_id = ui.set_root(MainScreen::new());
+ let buffer_id =
+ ui.add_child(root_id, BufferComponent::new(self.buffer.as_mut().unwrap()));
+ // let root_id = ui.set_root(Buffer::new(&mut typed_text));
+ ui.add_child(root_id, StatusLine::new());
+ ui.set_focus(buffer_id);
+
+ loop {
+ ui.process_event_queue()?;
+
+ // After updating and processing all of the widgets, compose them
+ // and render them to the screen.
+ if ui.render_to_screen(buf)? {
+ // We have more events to process immediately; don't block waiting
+ // for input below, but jump to the top of the loop to re-run the
+ // updates.
+ continue;
+ }
+ // Compute an optimized delta to apply to the terminal and display it
+ buf.flush()?;
+
+ // Wait for user input
+ match buf.terminal().poll_input(None) {
+ Ok(Some(InputEvent::Resized { rows, cols })) => {
+ // FIXME: this is working around a bug where we don't realize
+ // that we should redraw everything on resize in BufferedTerminal.
+ buf.add_change(Change::ClearScreen(Default::default()));
+ buf.resize(cols, rows);
+ }
+ Ok(Some(input)) => match input {
+ InputEvent::Key(KeyEvent {
+ key: KeyCode::Escape,
+ ..
+ }) => {
+ // Quit the app when escape is pressed
+ break;
+ }
+ input @ _ => {
+ // Feed input into the Ui
+ ui.queue_event(WidgetEvent::Input(input));
+ }
+ },
+ Ok(None) => {}
+ Err(e) => {
+ print!("{:?}\r\n", e);
+ break;
+ }
+ }
+ }
+ }
+
+ // After we've stopped the full screen raw terminal,
+ // print out the final edited value of the input text.
+ println!("The text you entered: {}", typed_text);
+ Ok(())
+ }
+}
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index e13309eb..9dfa3767 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -1,227 +1,24 @@
-//! This example shows how to make a basic widget that accumulates
-//! text input and renders it to the screen
-#![allow(unused)]
-use anyhow::Error;
-use termwiz::caps::Capabilities;
-use termwiz::cell::AttributeChange;
-use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
-use termwiz::input::*;
-use termwiz::surface::Change;
-use termwiz::terminal::buffered::BufferedTerminal;
-use termwiz::terminal::{new_terminal, Terminal};
-use termwiz::widgets::*;
-
-/// This is a widget for our application
-struct MainScreen {}
-
-impl MainScreen {
- pub fn new() -> Self {
- Self {}
- }
-}
-
-impl Widget for MainScreen {
- fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
- true // handled it all
- }
-
- /// Draw ourselves into the surface provided by RenderArgs
- fn render(&mut self, args: &mut RenderArgs) {
- // args.surface.add_change(Change::ClearScreen(
- // ColorAttribute::TrueColorWithPaletteFallback(
- // RgbColor::new(0x31, 0x1B, 0x92),
- // AnsiColor::Black.into(),
- // ),
- // ));
- // args.surface
- // .add_change(Change::Attribute(AttributeChange::Foreground(
- // ColorAttribute::TrueColorWithPaletteFallback(
- // RgbColor::new(0xB3, 0x88, 0xFF),
- // AnsiColor::Purple.into(),
- // ),
- // )));
- }
-
- fn get_size_constraints(&self) -> layout::Constraints {
- let mut constraints = layout::Constraints::default();
- constraints.child_orientation = layout::ChildOrientation::Vertical;
- constraints
- }
-}
-
-struct Buffer<'a> {
- /// Holds the input text that we wish the widget to display
- text: &'a mut String,
-}
-
-impl<'a> Buffer<'a> {
- /// Initialize the widget with the input text
- pub fn new(text: &'a mut String) -> Self {
- Self { text }
- }
-}
-
-impl<'a> Widget for Buffer<'a> {
- fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
- match event {
- WidgetEvent::Input(InputEvent::Key(KeyEvent {
- key: KeyCode::Char(c),
- ..
- })) => self.text.push(*c),
- WidgetEvent::Input(InputEvent::Key(KeyEvent {
- key: KeyCode::Enter,
- ..
- })) => {
- self.text.push_str("\r\n");
- }
- WidgetEvent::Input(InputEvent::Paste(s)) => {
- self.text.push_str(&s);
- }
- _ => {}
- }
-
- true // handled it all
- }
+mod editor;
- /// Draw ourselves into the surface provided by RenderArgs
- fn render(&mut self, args: &mut RenderArgs) {
- args.surface
- .add_change(Change::ClearScreen(ColorAttribute::Default));
+use editor::Editor;
- // args.surface
- // .add_change(Change::Attribute(AttributeChange::Foreground(
- // ColorAttribute::TrueColorWithPaletteFallback(
- // RgbColor::new(0x11, 0x00, 0xFF),
- // AnsiColor::Purple.into(),
- // ),
- // )));
- let dims = args.surface.dimensions();
- args.surface
- .add_change(format!("🤷 surface size is {:?}\r\n", dims));
- args.surface.add_change(self.text.clone());
+use argh::FromArgs;
+use std::{env, path::PathBuf};
- // Place the cursor at the end of the text.
- // A more advanced text editing widget would manage the
- // cursor position differently.
- *args.cursor = CursorShapeAndPosition {
- coords: args.surface.cursor_position().into(),
- shape: termwiz::surface::CursorShape::SteadyBar,
- ..Default::default()
- };
- }
-
- fn get_size_constraints(&self) -> layout::Constraints {
- let mut c = layout::Constraints::default();
- c.set_valign(layout::VerticalAlignment::Top);
- c
- }
-}
-
-struct StatusLine {}
-
-impl StatusLine {
- pub fn new() -> Self {
- StatusLine {}
- }
-}
-impl Widget for StatusLine {
- fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
- true
- }
-
- fn render(&mut self, args: &mut RenderArgs) {
- args.surface.add_change(Change::ClearScreen(
- ColorAttribute::TrueColorWithPaletteFallback(
- RgbColor::new(0xFF, 0xFF, 0xFF),
- AnsiColor::Black.into(),
- ),
- ));
- args.surface
- .add_change(Change::Attribute(AttributeChange::Foreground(
- ColorAttribute::TrueColorWithPaletteFallback(
- RgbColor::new(0x00, 0x00, 0x00),
- AnsiColor::Black.into(),
- ),
- )));
-
- args.surface.add_change(" helix");
- }
+use anyhow::Error;
- fn get_size_constraints(&self) -> layout::Constraints {
- *layout::Constraints::default()
- .set_fixed_height(1)
- .set_valign(layout::VerticalAlignment::Bottom)
- }
+#[derive(FromArgs)]
+/// A post-modern text editor.
+pub struct Args {
+ #[argh(positional)]
+ files: Vec<PathBuf>,
}
fn main() -> Result<(), Error> {
- // Start with an empty string; typing into the app will
- // update this string.
- let mut typed_text = String::new();
-
- {
- // Create a terminal and put it into full screen raw mode
- let caps = Capabilities::new_from_env()?;
- let mut buf = BufferedTerminal::new(new_terminal(caps)?)?;
- buf.terminal().enter_alternate_screen()?;
- buf.terminal().set_raw_mode()?;
-
- // Set up the UI
- let mut ui = Ui::new();
-
- let root_id = ui.set_root(MainScreen::new());
- let buffer_id = ui.add_child(root_id, Buffer::new(&mut typed_text));
- // let root_id = ui.set_root(Buffer::new(&mut typed_text));
- ui.add_child(root_id, StatusLine::new());
- ui.set_focus(buffer_id);
-
- loop {
- ui.process_event_queue()?;
-
- // After updating and processing all of the widgets, compose them
- // and render them to the screen.
- if ui.render_to_screen(&mut buf)? {
- // We have more events to process immediately; don't block waiting
- // for input below, but jump to the top of the loop to re-run the
- // updates.
- continue;
- }
- // Compute an optimized delta to apply to the terminal and display it
- buf.flush()?;
-
- // Wait for user input
- match buf.terminal().poll_input(None) {
- Ok(Some(InputEvent::Resized { rows, cols })) => {
- // FIXME: this is working around a bug where we don't realize
- // that we should redraw everything on resize in BufferedTerminal.
- buf.add_change(Change::ClearScreen(Default::default()));
- buf.resize(cols, rows);
- }
- Ok(Some(input)) => match input {
- InputEvent::Key(KeyEvent {
- key: KeyCode::Escape,
- ..
- }) => {
- // Quit the app when escape is pressed
- break;
- }
- input @ _ => {
- // Feed input into the Ui
- ui.queue_event(WidgetEvent::Input(input));
- }
- },
- Ok(None) => {}
- Err(e) => {
- print!("{:?}\r\n", e);
- break;
- }
- }
- }
- }
+ let args: Args = argh::from_env();
+ let mut editor = Editor::new(args)?;
- // After we've stopped the full screen raw terminal,
- // print out the final edited value of the input text.
- println!("The text you entered: {}", typed_text);
+ editor.run()?;
Ok(())
}