aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/.gitignore1
-rw-r--r--helix-term/Cargo.toml19
-rw-r--r--helix-term/README.md7
-rw-r--r--helix-term/src/line.rs111
-rw-r--r--helix-term/src/main.rs227
5 files changed, 365 insertions, 0 deletions
diff --git a/helix-term/.gitignore b/helix-term/.gitignore
new file mode 100644
index 00000000..ea8c4bf7
--- /dev/null
+++ b/helix-term/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
new file mode 100644
index 00000000..f6528034
--- /dev/null
+++ b/helix-term/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "helix-term"
+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
+
+[[bin]]
+name = "hx"
+path = "src/main.rs"
+
+# [[bin]]
+# name = "line"
+# path = "src/line.rs"
+
+[dependencies]
+termwiz = { git = "https://github.com/wez/wezterm", features = ["widgets"] }
+anyhow = "1.0.31"
diff --git a/helix-term/README.md b/helix-term/README.md
new file mode 100644
index 00000000..abefa6fd
--- /dev/null
+++ b/helix-term/README.md
@@ -0,0 +1,7 @@
+
+window -> buffer -> text
+\-> contains "view", a viewport into the buffer
+
+view
+\-> selections etc
+-> cursor
diff --git a/helix-term/src/line.rs b/helix-term/src/line.rs
new file mode 100644
index 00000000..90fb5e6f
--- /dev/null
+++ b/helix-term/src/line.rs
@@ -0,0 +1,111 @@
+use termwiz::cell::AttributeChange;
+use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
+use termwiz::lineedit::*;
+
+#[derive(Default)]
+struct Host {
+ history: BasicHistory,
+}
+
+impl LineEditorHost for Host {
+ // Render the prompt with a darkslateblue background color if
+ // the terminal supports true color, otherwise render it with
+ // a navy blue ansi color.
+ fn render_prompt(&self, prompt: &str) -> Vec<OutputElement> {
+ vec![
+ OutputElement::Attribute(AttributeChange::Background(
+ ColorAttribute::TrueColorWithPaletteFallback(
+ RgbColor::from_named("darkslateblue").unwrap(),
+ AnsiColor::Navy.into(),
+ ),
+ )),
+ OutputElement::Text(prompt.to_owned()),
+ ]
+ }
+
+ fn history(&mut self) -> &mut dyn History {
+ &mut self.history
+ }
+
+ /// Demo of the completion API for words starting with "h" or "he"
+ fn complete(&self, line: &str, cursor_position: usize) -> Vec<CompletionCandidate> {
+ let mut candidates = vec![];
+ if let Some((range, word)) = word_at_cursor(line, cursor_position) {
+ let words = &["hello", "help", "he-man"];
+
+ for w in words {
+ if w.starts_with(word) {
+ candidates.push(CompletionCandidate {
+ range: range.clone(),
+ text: w.to_string(),
+ });
+ }
+ }
+ }
+ candidates
+ }
+}
+
+/// This is a conceptually simple function that computes the bounds
+/// of the whitespace delimited word at the specified cursor position
+/// in the supplied line string.
+/// It returns the range and the corresponding slice out of the line.
+/// This function is sufficient for example purposes; in a real application
+/// the equivalent function would need to be aware of quoting and other
+/// application specific context.
+fn word_at_cursor(line: &str, cursor_position: usize) -> Option<(std::ops::Range<usize>, &str)> {
+ let char_indices: Vec<(usize, char)> = line.char_indices().collect();
+ if char_indices.is_empty() {
+ return None;
+ }
+ let char_position = char_indices
+ .iter()
+ .position(|(idx, _)| *idx == cursor_position)
+ .unwrap_or(char_indices.len());
+
+ // Look back until we find whitespace
+ let mut start_position = char_position;
+ while start_position > 0
+ && start_position <= char_indices.len()
+ && !char_indices[start_position - 1].1.is_whitespace()
+ {
+ start_position -= 1;
+ }
+
+ // Look forwards until we find whitespace
+ let mut end_position = char_position;
+ while end_position < char_indices.len() && !char_indices[end_position].1.is_whitespace() {
+ end_position += 1;
+ }
+
+ if end_position > start_position {
+ let range = char_indices[start_position].0
+ ..char_indices
+ .get(end_position)
+ .map(|c| c.0 + 1)
+ .unwrap_or(line.len());
+ Some((range.clone(), &line[range]))
+ } else {
+ None
+ }
+}
+
+fn main() -> anyhow::Result<()> {
+ println!("Type `exit` to quit this example, or start a word with `h` and press Tab.");
+ let mut terminal = line_editor_terminal()?;
+ let mut editor = LineEditor::new(&mut terminal);
+
+ let mut host = Host::default();
+ loop {
+ if let Some(line) = editor.read_line(&mut host)? {
+ println!("read line: {:?}", line);
+ if line == "exit" {
+ break;
+ }
+
+ host.history().add(&line);
+ }
+ }
+
+ Ok(())
+}
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
new file mode 100644
index 00000000..e13309eb
--- /dev/null
+++ b/helix-term/src/main.rs
@@ -0,0 +1,227 @@
+//! 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
+ }
+
+ /// 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 dims = args.surface.dimensions();
+ 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
+ }
+}
+
+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)
+ }
+}
+
+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;
+ }
+ }
+ }
+ }
+
+ // 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(())
+}