summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2020-05-25 04:02:21 +0000
committerBlaž Hrastnik2020-05-25 04:02:21 +0000
commit44ff4d3c1f5da05e57ce99ba9d67b80a334def83 (patch)
tree232b8eebab7f709eaf84b8649791a6c74448bfdb
parent240e5f4e3d27415b792776dd126d15302d53e83b (diff)
Implement a new core based on CodeMirror.
-rw-r--r--Cargo.lock223
-rw-r--r--README.md1
-rw-r--r--helix-core/Cargo.toml2
-rw-r--r--helix-core/src/buffer.rs18
-rw-r--r--helix-core/src/lib.rs16
-rw-r--r--helix-core/src/position.rs27
-rw-r--r--helix-core/src/range.rs9
-rw-r--r--helix-core/src/selection.rs222
-rw-r--r--helix-core/src/state.rs38
-rw-r--r--helix-core/src/transaction.rs25
-rw-r--r--helix-term/Cargo.toml5
-rw-r--r--helix-term/src/editor.rs281
-rw-r--r--helix-term/src/main.rs229
13 files changed, 675 insertions, 421 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3d182e52..defeb699 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,21 +22,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
[[package]]
-name = "arrayref"
-version = "0.3.6"
+name = "argh"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+checksum = "ca1877e24cecacd700d469066e0160c4f8497cc5635367163f50c8beec820154"
+dependencies = [
+ "argh_derive",
+ "argh_shared",
+]
[[package]]
-name = "arrayvec"
-version = "0.4.12"
+name = "argh_derive"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
+checksum = "e742194e0f43fc932bcb801708c2b279d3ec8f527e3acda05a6a9f342c5ef764"
dependencies = [
- "nodrop",
+ "argh_shared",
+ "heck",
+ "proc-macro2 1.0.13",
+ "quote 1.0.6",
+ "syn 1.0.22",
]
[[package]]
+name = "argh_shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1ba68f4276a778591e36a0c348a269888f3a177c8d2054969389e3b59611ff5"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -76,7 +96,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
dependencies = [
"arrayref",
- "arrayvec 0.5.1",
+ "arrayvec",
"constant_time_eq",
]
@@ -140,11 +160,9 @@ dependencies = [
[[package]]
name = "filedescriptor"
version = "0.7.1"
-source = "git+https://github.com/wez/wezterm#58686f925f0f4a0942452e8feb0ababd48ec936c"
dependencies = [
"anyhow",
"libc",
- "thiserror",
"winapi",
]
@@ -166,14 +184,30 @@ dependencies = [
]
[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
name = "helix-core"
version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "ropey",
+ "smallvec 1.4.0",
+]
[[package]]
name = "helix-term"
version = "0.1.0"
dependencies = [
"anyhow",
+ "argh",
+ "helix-core",
"termwiz",
]
@@ -184,20 +218,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
-name = "lexical-core"
-version = "0.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f"
-dependencies = [
- "arrayvec 0.4.12",
- "bitflags",
- "cfg-if",
- "rustc_version",
- "ryu",
- "static_assertions",
-]
-
-[[package]]
name = "libc"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -231,58 +251,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
[[package]]
-name = "nodrop"
-version = "0.1.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
-
-[[package]]
name = "nom"
version = "5.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
dependencies = [
- "lexical-core",
"memchr",
"version_check",
]
[[package]]
-name = "num"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
-dependencies = [
- "num-bigint",
- "num-complex",
- "num-integer",
- "num-iter",
- "num-rational",
- "num-traits",
-]
-
-[[package]]
-name = "num-bigint"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-complex"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
name = "num-derive"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -294,39 +272,6 @@ dependencies = [
]
[[package]]
-name = "num-integer"
-version = "0.1.42"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
-name = "num-iter"
-version = "0.1.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-rational"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
-dependencies = [
- "autocfg",
- "num-bigint",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
name = "num-traits"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -511,6 +456,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
[[package]]
+name = "ropey"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba326a8508a4add47e7b260333aa2d896213a5f3572fde11ed6e9130241b7f71"
+dependencies = [
+ "smallvec 0.6.13",
+]
+
+[[package]]
name = "rust-argon2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -523,21 +477,6 @@ dependencies = [
]
[[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-dependencies = [
- "semver",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
-
-[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -553,26 +492,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
-name = "serde"
-version = "1.0.110"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.110"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
-dependencies = [
- "proc-macro2 1.0.13",
- "quote 1.0.6",
- "syn 1.0.22",
-]
-
-[[package]]
name = "signal-hook"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -608,10 +527,10 @@ dependencies = [
]
[[package]]
-name = "static_assertions"
-version = "0.3.4"
+name = "smallvec"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
+checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "syn"
@@ -637,9 +556,9 @@ dependencies = [
[[package]]
name = "terminfo"
-version = "0.7.2"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12064715207074ac562f450722884f981268a916ce1eb21c5bda2c806c8fecfc"
+checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
dependencies = [
"dirs",
"fnv",
@@ -660,7 +579,6 @@ dependencies = [
[[package]]
name = "termwiz"
version = "0.9.0"
-source = "git+https://github.com/wez/wezterm#58686f925f0f4a0942452e8feb0ababd48ec936c"
dependencies = [
"anyhow",
"base64 0.10.1",
@@ -672,15 +590,13 @@ dependencies = [
"libc",
"log",
"memmem",
- "num",
"num-derive",
"num-traits",
"ordered-float",
"regex",
"semver",
- "serde",
"signal-hook",
- "smallvec",
+ "smallvec 0.6.13",
"terminfo",
"termios",
"unicode-segmentation",
@@ -691,26 +607,6 @@ dependencies = [
]
[[package]]
-name = "thiserror"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d"
-dependencies = [
- "proc-macro2 1.0.13",
- "quote 1.0.6",
- "syn 1.0.22",
-]
-
-[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -758,7 +654,6 @@ checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
[[package]]
name = "vtparse"
version = "0.2.2"
-source = "git+https://github.com/wez/wezterm#58686f925f0f4a0942452e8feb0ababd48ec936c"
dependencies = [
"utf8parse",
]
diff --git a/README.md b/README.md
index 70eca234..325ff522 100644
--- a/README.md
+++ b/README.md
@@ -78,3 +78,4 @@ conceal for markdown markers, etc
codemirror uses offsets exclusively with Line being computed when necessary
(with start/end extents)
+lines are temporarily cached in a lineCache
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index 446e8e42..f1f6264f 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -8,4 +8,6 @@ edition = "2018"
[dependencies]
ropey = "1.1.0"
+anyhow = "1.0.31"
+smallvec = "1.4.0"
# slab = "0.4.2"
diff --git a/helix-core/src/buffer.rs b/helix-core/src/buffer.rs
new file mode 100644
index 00000000..9dd22773
--- /dev/null
+++ b/helix-core/src/buffer.rs
@@ -0,0 +1,18 @@
+use anyhow::Error;
+use ropey::Rope;
+use std::{env, fs::File, io::BufReader, path::PathBuf};
+
+pub struct Buffer {
+ pub contents: Rope,
+}
+
+impl Buffer {
+ pub fn load(path: PathBuf) -> Result<Self, Error> {
+ let current_dir = env::current_dir()?;
+
+ let contents = Rope::from_reader(BufReader::new(File::open(path)?))?;
+
+ // TODO: create if not found
+ Ok(Buffer { contents })
+ }
+}
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 71a66030..ceed961f 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -1,5 +1,13 @@
-mod position;
-mod range;
+mod buffer;
+mod selection;
+mod state;
+mod transaction;
-use position::Position;
-use range::Range;
+pub use buffer::Buffer;
+
+pub use selection::Range as SelectionRange;
+pub use selection::Selection;
+
+pub use state::State;
+
+pub use transaction::{Change, Transaction};
diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs
deleted file mode 100644
index 8c82b83b..00000000
--- a/helix-core/src/position.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-/// Represents a single point in a text buffer. Zero indexed.
-#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Position {
- pub row: usize,
- pub col: usize,
-}
-
-impl Position {
- pub fn new(row: usize, col: usize) -> Self {
- Self { row, col }
- }
-
- pub fn is_zero(self) -> bool {
- self.row == 0 && self.col == 0
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_ordering() {
- // (0, 5) is less than (1, 0 w v f)
- assert!(Position::new(0, 5) < Position::new(1, 0));
- }
-}
diff --git a/helix-core/src/range.rs b/helix-core/src/range.rs
deleted file mode 100644
index 46411664..00000000
--- a/helix-core/src/range.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use crate::Position;
-
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub struct Range {
- pub start: Position,
- pub end: Position,
-}
-
-// range traversal iters
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
new file mode 100644
index 00000000..24a8be46
--- /dev/null
+++ b/helix-core/src/selection.rs
@@ -0,0 +1,222 @@
+//! Selections are the primary editing construct. Even a single cursor is defined as an empty
+//! single selection range.
+//!
+//! All positioning is done via `char` offsets into the buffer.
+use smallvec::{smallvec, SmallVec};
+
+#[inline]
+fn abs_difference(x: usize, y: usize) -> usize {
+ if x < y {
+ y - x
+ } else {
+ x - y
+ }
+}
+
+/// A single selection range. Anchor-inclusive, head-exclusive.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Range {
+ // TODO: optimize into u32
+ /// The anchor of the range: the side that doesn't move when extending.
+ pub anchor: usize,
+ /// The head of the range, moved when extending.
+ pub head: usize,
+}
+
+impl Range {
+ pub fn new(anchor: usize, head: usize) -> Self {
+ Self { anchor, head }
+ }
+
+ /// Start of the range.
+ #[inline]
+ pub fn from(&self) -> usize {
+ std::cmp::min(self.anchor, self.head)
+ }
+
+ /// End of the range.
+ #[inline]
+ pub fn to(&self) -> usize {
+ std::cmp::max(self.anchor, self.head)
+ }
+
+ /// `true` when head and anchor are at the same position.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.anchor == self.head
+ }
+
+ /// Check two ranges for overlap.
+ pub fn overlaps(&self, other: &Self) -> bool {
+ // cursor overlap is checked differently
+ if self.is_empty() {
+ self.from() <= other.to()
+ } else {
+ self.from() < other.to()
+ }
+ }
+
+ // TODO: map
+
+ /// Extend the range to cover at least `from` `to`.
+ pub fn extend(&self, from: usize, to: usize) -> Self {
+ if from <= self.anchor && to >= self.anchor {
+ return Range {
+ anchor: from,
+ head: to,
+ };
+ }
+
+ Range {
+ anchor: self.anchor,
+ head: if abs_difference(from, self.anchor) > abs_difference(to, self.anchor) {
+ from
+ } else {
+ to
+ },
+ }
+ }
+
+ // groupAt
+}
+
+/// A selection consists of one or more selection ranges.
+pub struct Selection {
+ // TODO: decide how many ranges to inline SmallVec<[Range; 1]>
+ ranges: Vec<Range>,
+ primary_index: usize,
+}
+
+impl Selection {
+ // map
+ // eq
+ pub fn primary(&self) -> Range {
+ self.ranges[self.primary_index]
+ }
+
+ /// Ensure selection containing only the primary selection.
+ pub fn as_single(self) -> Self {
+ if self.ranges.len() == 1 {
+ self
+ } else {
+ Self {
+ ranges: vec![self.ranges[self.primary_index]],
+ primary_index: 0,
+ }
+ }
+ }
+
+ // add_range // push
+ // replace_range
+
+ /// Constructs a selection holding a single range.
+ pub fn single(anchor: usize, head: usize) -> Self {
+ Self {
+ ranges: vec![Range { anchor, head }],
+ primary_index: 0,
+ }
+ }
+
+ pub fn new(ranges: Vec<Range>, primary_index: usize) -> Self {
+ fn normalize(mut ranges: Vec<Range>, primary_index: usize) -> Selection {
+ let primary = ranges[primary_index];
+ ranges.sort_unstable_by_key(|range| range.from());
+ let mut primary_index = ranges.iter().position(|&range| range == primary).unwrap();
+
+ let mut result: Vec<Range> = Vec::new();
+
+ // TODO: we could do with one vec by removing elements as we mutate
+
+ for (i, range) in ranges.into_iter().enumerate() {
+ // if previous value exists
+ if let Some(prev) = result.last_mut() {
+ // and we overlap it
+ if range.overlaps(prev) {
+ let from = prev.from();
+ let to = std::cmp::max(range.to(), prev.to());
+
+ if i <= primary_index {
+ primary_index -= 1
+ }
+
+ // merge into previous
+ if range.anchor > range.head {
+ prev.anchor = to;
+ prev.head = from;
+ } else {
+ prev.anchor = from;
+ prev.head = to;
+ }
+ continue;
+ }
+ }
+
+ result.push(range)
+ }
+
+ Selection {
+ ranges: result,
+ primary_index,
+ }
+ }
+
+ // TODO: only normalize if needed (any ranges out of order)
+ normalize(ranges, primary_index)
+ }
+}
+
+// TODO: checkSelection -> check if valid for doc length
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_create_normalizes_and_merges() {
+ let sel = Selection::new(
+ vec![
+ Range::new(10, 12),
+ Range::new(6, 7),
+ Range::new(4, 5),
+ Range::new(3, 4),
+ Range::new(0, 6),
+ Range::new(7, 8),
+ Range::new(9, 13),
+ Range::new(13, 14),
+ ],
+ 0,
+ );
+
+ let res = sel
+ .ranges
+ .into_iter()
+ .map(|range| format!("{}/{}", range.anchor, range.head))
+ .collect::<Vec<String>>()
+ .join(",");
+
+ assert_eq!(res, "0/6,6/7,7/8,9/13,13/14");
+ }
+
+ #[test]
+ fn test_create_merges_adjacent_points() {
+ let sel = Selection::new(
+ vec![
+ Range::new(10, 12),
+ Range::new(12, 12),
+ Range::new(12, 12),
+ Range::new(10, 10),
+ Range::new(8, 10),
+ ],
+ 0,
+ );
+
+ let res = sel
+ .ranges
+ .into_iter()
+ .map(|range| format!("{}/{}", range.anchor, range.head))
+ .collect::<Vec<String>>()
+ .join(",");
+
+ assert_eq!(res, "8/10,10/12");
+ }
+}
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
new file mode 100644
index 00000000..81b8e108
--- /dev/null
+++ b/helix-core/src/state.rs
@@ -0,0 +1,38 @@
+use crate::{Buffer, Selection};
+
+/// A state represents the current editor state of a single buffer.
+pub struct State {
+ // TODO: maybe doc: ?
+ buffer: Buffer,
+ selection: Selection,
+}
+
+impl State {
+ pub fn new(buffer: Buffer) -> Self {
+ Self {
+ buffer,
+ selection: Selection::single(0, 0),
+ }
+ }
+
+ // TODO: buf/selection accessors
+
+ // update/transact
+ // replaceSelection (transaction that replaces selection)
+ // changeByRange
+ // changes
+ // slice
+ //
+ // getters:
+ // tabSize
+ // indentUnit
+ // languageDataAt()
+ //
+ // config:
+ // indentation
+ // tabSize
+ // lineUnit
+ // syntax
+ // foldable
+ // changeFilter/transactionFilter
+}
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
new file mode 100644
index 00000000..ecbe0c50
--- /dev/null
+++ b/helix-core/src/transaction.rs
@@ -0,0 +1,25 @@
+pub struct Change {
+ from: usize,
+ to: usize,
+ insert: Option<String>,
+}
+
+impl Change {
+ pub fn new(from: usize, to: usize, insert: Option<String>) {
+ // old_extent, new_extent, insert
+ }
+}
+
+pub struct Transaction {}
+
+// ChangeSpec = Change | ChangeSet | Vec<Change>
+// ChangeDesc as a ChangeSet without text: can't be applied, cheaper to store.
+// ChangeSet = ChangeDesc with Text
+pub struct ChangeSet {
+ // basically Vec<ChangeDesc> where ChangeDesc = (current len, replacement len?)
+ // (0, n>0) for insertion, (n>0, 0) for deletion, (>0, >0) for replacement
+ sections: Vec<(usize, isize)>,
+}
+//
+// trait Transaction
+// trait StrictTransaction
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(())
}