on master help sign in
Implement a new core based on CodeMirror.
commit44ff4d3c1f5da05e57ce99ba9d67b80a334def83 authorBlaž Hrastnik <blaz@mxxn.io> dateMon 25 May 2020, 13:02 (6 years ago) parent240e5f4
13 files changed+675−421
Cargo.lock+59−164 README.md+1 helix-core/Cargo.toml+2 helix-core/src/buffer.rs+18 helix-core/src/lib.rs+12−4 helix-core/src/position.rs−27 helix-core/src/range.rs−9 helix-core/src/selection.rs+222 helix-core/src/state.rs+38 helix-core/src/transaction.rs+25 helix-term/Cargo.toml+4−1 helix-term/src/editor.rs+281 helix-term/src/main.rs+13−216
Cargo.lock+59−164
@@ -22,20 +22,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2222checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
2323
2424[[package]]
25−name = "arrayref"
26−version = "0.3.6"
25+name = "argh"
26+version = "0.1.3"
2727source = "registry+https://github.com/rust-lang/crates.io-index"
28−checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
28+checksum = "ca1877e24cecacd700d469066e0160c4f8497cc5635367163f50c8beec820154"
29+dependencies = [
30+ "argh_derive",
31+ "argh_shared",
32+]
2933
3034[[package]]
31−name = "arrayvec"
32−version = "0.4.12"
35+name = "argh_derive"
36+version = "0.1.1"
3337source = "registry+https://github.com/rust-lang/crates.io-index"
34−checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
38+checksum = "e742194e0f43fc932bcb801708c2b279d3ec8f527e3acda05a6a9f342c5ef764"
3539dependencies = [
36− "nodrop",
40+ "argh_shared",
41+ "heck",
42+ "proc-macro2 1.0.13",
43+ "quote 1.0.6",
44+ "syn 1.0.22",
3745]
3846
47+[[package]]
48+name = "argh_shared"
49+version = "0.1.1"
50+source = "registry+https://github.com/rust-lang/crates.io-index"
51+checksum = "e1ba68f4276a778591e36a0c348a269888f3a177c8d2054969389e3b59611ff5"
52+
53+[[package]]
54+name = "arrayref"
55+version = "0.3.6"
56+source = "registry+https://github.com/rust-lang/crates.io-index"
57+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
58+
3959[[package]]
4060name = "arrayvec"
4161version = "0.5.1"
@@ -76,7 +96,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
7696checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
7797dependencies = [
7898 "arrayref",
79− "arrayvec 0.5.1",
99+ "arrayvec",
80100 "constant_time_eq",
81101]
82102
@@ -140,11 +160,9 @@ dependencies = [
140160[[package]]
141161name = "filedescriptor"
142162version = "0.7.1"
143−source = "git+https://github.com/wez/wezterm#58686f925f0f4a0942452e8feb0ababd48ec936c"
144163dependencies = [
145164 "anyhow",
146165 "libc",
147− "thiserror",
148166 "winapi",
149167]
150168
@@ -165,15 +183,31 @@ dependencies = [
165183 "wasi",
166184]
167185
186+[[package]]
187+name = "heck"
188+version = "0.3.1"
189+source = "registry+https://github.com/rust-lang/crates.io-index"
190+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
191+dependencies = [
192+ "unicode-segmentation",
193+]
194+
168195[[package]]
169196name = "helix-core"
170197version = "0.1.0"
198+dependencies = [
199+ "anyhow",
200+ "ropey",
201+ "smallvec 1.4.0",
202+]
171203
172204[[package]]
173205name = "helix-term"
174206version = "0.1.0"
175207dependencies = [
176208 "anyhow",
209+ "argh",
210+ "helix-core",
177211 "termwiz",
178212]
179213
@@ -183,20 +217,6 @@ version = "1.4.0"
183217source = "registry+https://github.com/rust-lang/crates.io-index"
184218checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
185219
186−[[package]]
187−name = "lexical-core"
188−version = "0.6.7"
189−source = "registry+https://github.com/rust-lang/crates.io-index"
190−checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f"
191−dependencies = [
192− "arrayvec 0.4.12",
193− "bitflags",
194− "cfg-if",
195− "rustc_version",
196− "ryu",
197− "static_assertions",
198−]
199−
200220[[package]]
201221name = "libc"
202222version = "0.2.70"
@@ -230,58 +250,16 @@ version = "0.1.1"
230250source = "registry+https://github.com/rust-lang/crates.io-index"
231251checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
232252
233−[[package]]
234−name = "nodrop"
235−version = "0.1.14"
236−source = "registry+https://github.com/rust-lang/crates.io-index"
237−checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
238−
239253[[package]]
240254name = "nom"
241255version = "5.1.1"
242256source = "registry+https://github.com/rust-lang/crates.io-index"
243257checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
244258dependencies = [
245− "lexical-core",
246259 "memchr",
247260 "version_check",
248261]
249262
250−[[package]]
251−name = "num"
252−version = "0.2.1"
253−source = "registry+https://github.com/rust-lang/crates.io-index"
254−checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
255−dependencies = [
256− "num-bigint",
257− "num-complex",
258− "num-integer",
259− "num-iter",
260− "num-rational",
261− "num-traits",
262−]
263−
264−[[package]]
265−name = "num-bigint"
266−version = "0.2.6"
267−source = "registry+https://github.com/rust-lang/crates.io-index"
268−checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
269−dependencies = [
270− "autocfg",
271− "num-integer",
272− "num-traits",
273−]
274−
275−[[package]]
276−name = "num-complex"
277−version = "0.2.4"
278−source = "registry+https://github.com/rust-lang/crates.io-index"
279−checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
280−dependencies = [
281− "autocfg",
282− "num-traits",
283−]
284−
285263[[package]]
286264name = "num-derive"
287265version = "0.2.5"
@@ -293,39 +271,6 @@ dependencies = [
293271 "syn 0.15.44",
294272]
295273
296−[[package]]
297−name = "num-integer"
298−version = "0.1.42"
299−source = "registry+https://github.com/rust-lang/crates.io-index"
300−checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
301−dependencies = [
302− "autocfg",
303− "num-traits",
304−]
305−
306−[[package]]
307−name = "num-iter"
308−version = "0.1.40"
309−source = "registry+https://github.com/rust-lang/crates.io-index"
310−checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
311−dependencies = [
312− "autocfg",
313− "num-integer",
314− "num-traits",
315−]
316−
317−[[package]]
318−name = "num-rational"
319−version = "0.2.4"
320−source = "registry+https://github.com/rust-lang/crates.io-index"
321−checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
322−dependencies = [
323− "autocfg",
324− "num-bigint",
325− "num-integer",
326− "num-traits",
327−]
328−
329274[[package]]
330275name = "num-traits"
331276version = "0.2.11"
@@ -510,6 +455,15 @@ version = "0.6.17"
510455source = "registry+https://github.com/rust-lang/crates.io-index"
511456checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
512457
458+[[package]]
459+name = "ropey"
460+version = "1.1.0"
461+source = "registry+https://github.com/rust-lang/crates.io-index"
462+checksum = "ba326a8508a4add47e7b260333aa2d896213a5f3572fde11ed6e9130241b7f71"
463+dependencies = [
464+ "smallvec 0.6.13",
465+]
466+
513467[[package]]
514468name = "rust-argon2"
515469version = "0.7.0"
@@ -522,21 +476,6 @@ dependencies = [
522476 "crossbeam-utils",
523477]
524478
525−[[package]]
526−name = "rustc_version"
527−version = "0.2.3"
528−source = "registry+https://github.com/rust-lang/crates.io-index"
529−checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
530−dependencies = [
531− "semver",
532−]
533−
534−[[package]]
535−name = "ryu"
536−version = "1.0.4"
537−source = "registry+https://github.com/rust-lang/crates.io-index"
538−checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
539−
540479[[package]]
541480name = "semver"
542481version = "0.9.0"
@@ -552,26 +491,6 @@ version = "0.7.0"
552491source = "registry+https://github.com/rust-lang/crates.io-index"
553492checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
554493
555−[[package]]
556−name = "serde"
557−version = "1.0.110"
558−source = "registry+https://github.com/rust-lang/crates.io-index"
559−checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
560−dependencies = [
561− "serde_derive",
562−]
563−
564−[[package]]
565−name = "serde_derive"
566−version = "1.0.110"
567−source = "registry+https://github.com/rust-lang/crates.io-index"
568−checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
569−dependencies = [
570− "proc-macro2 1.0.13",
571− "quote 1.0.6",
572− "syn 1.0.22",
573−]
574−
575494[[package]]
576495name = "signal-hook"
577496version = "0.1.15"
@@ -608,10 +527,10 @@ dependencies = [
608527]
609528
610529[[package]]
611−name = "static_assertions"
612−version = "0.3.4"
530+name = "smallvec"
531+version = "1.4.0"
613532source = "registry+https://github.com/rust-lang/crates.io-index"
614−checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
533+checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
615534
616535[[package]]
617536name = "syn"
@@ -637,9 +556,9 @@ dependencies = [
637556
638557[[package]]
639558name = "terminfo"
640−version = "0.7.2"
559+version = "0.7.3"
641560source = "registry+https://github.com/rust-lang/crates.io-index"
642−checksum = "12064715207074ac562f450722884f981268a916ce1eb21c5bda2c806c8fecfc"
561+checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
643562dependencies = [
644563 "dirs",
645564 "fnv",
@@ -660,7 +579,6 @@ dependencies = [
660579[[package]]
661580name = "termwiz"
662581version = "0.9.0"
663−source = "git+https://github.com/wez/wezterm#58686f925f0f4a0942452e8feb0ababd48ec936c"
664582dependencies = [
665583 "anyhow",
666584 "base64 0.10.1",
@@ -672,15 +590,13 @@ dependencies = [
672590 "libc",
673591 "log",
674592 "memmem",
675− "num",
676593 "num-derive",
677594 "num-traits",
678595 "ordered-float",
679596 "regex",
680597 "semver",
681− "serde",
682598 "signal-hook",
683− "smallvec",
599+ "smallvec 0.6.13",
684600 "terminfo",
685601 "termios",
686602 "unicode-segmentation",
@@ -690,26 +606,6 @@ dependencies = [
690606 "xi-unicode",
691607]
692608
693−[[package]]
694−name = "thiserror"
695−version = "1.0.18"
696−source = "registry+https://github.com/rust-lang/crates.io-index"
697−checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7"
698−dependencies = [
699− "thiserror-impl",
700−]
701−
702−[[package]]
703−name = "thiserror-impl"
704−version = "1.0.18"
705−source = "registry+https://github.com/rust-lang/crates.io-index"
706−checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d"
707−dependencies = [
708− "proc-macro2 1.0.13",
709− "quote 1.0.6",
710− "syn 1.0.22",
711−]
712−
713609[[package]]
714610name = "thread_local"
715611version = "1.0.1"
@@ -758,7 +654,6 @@ checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
758654[[package]]
759655name = "vtparse"
760656version = "0.2.2"
761−source = "git+https://github.com/wez/wezterm#58686f925f0f4a0942452e8feb0ababd48ec936c"
762657dependencies = [
763658 "utf8parse",
764659]
README.md+1
@@ -78,3 +78,4 @@ conceal for markdown markers, etc
7878
7979codemirror uses offsets exclusively with Line being computed when necessary
8080(with start/end extents)
81+lines are temporarily cached in a lineCache
helix-core/Cargo.toml+2
@@ -8,4 +8,6 @@ edition = "2018"
88
99[dependencies]
1010ropey = "1.1.0"
11+anyhow = "1.0.31"
12+smallvec = "1.4.0"
1113# slab = "0.4.2"
helix-core/src/buffer.rsnew file+18
@@ -0,0 +1,18 @@
1+use anyhow::Error;
2+use ropey::Rope;
3+use std::{env, fs::File, io::BufReader, path::PathBuf};
4+
5+pub struct Buffer {
6+ pub contents: Rope,
7+}
8+
9+impl Buffer {
10+ pub fn load(path: PathBuf) -> Result<Self, Error> {
11+ let current_dir = env::current_dir()?;
12+
13+ let contents = Rope::from_reader(BufReader::new(File::open(path)?))?;
14+
15+ // TODO: create if not found
16+ Ok(Buffer { contents })
17+ }
18+}
helix-core/src/lib.rs+12−4
@@ -1,5 +1,13 @@
1−mod position;
2−mod range;
1+mod buffer;
2+mod selection;
3+mod state;
4+mod transaction;
35
4−use position::Position;
5−use range::Range;
6+pub use buffer::Buffer;
7+
8+pub use selection::Range as SelectionRange;
9+pub use selection::Selection;
10+
11+pub use state::State;
12+
13+pub use transaction::{Change, Transaction};
helix-core/src/position.rsdeleted−27
@@ -1,27 +0,0 @@
1−/// Represents a single point in a text buffer. Zero indexed.
2−#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3−pub struct Position {
4− pub row: usize,
5− pub col: usize,
6−}
7−
8−impl Position {
9− pub fn new(row: usize, col: usize) -> Self {
10− Self { row, col }
11− }
12−
13− pub fn is_zero(self) -> bool {
14− self.row == 0 && self.col == 0
15− }
16−}
17−
18−#[cfg(test)]
19−mod test {
20− use super::*;
21−
22− #[test]
23− fn test_ordering() {
24− // (0, 5) is less than (1, 0 w v f)
25− assert!(Position::new(0, 5) < Position::new(1, 0));
26− }
27−}
helix-core/src/range.rsdeleted−9
@@ -1,9 +0,0 @@
1−use crate::Position;
2−
3−#[derive(Clone, Copy, PartialEq, Eq)]
4−pub struct Range {
5− pub start: Position,
6− pub end: Position,
7−}
8−
9−// range traversal iters
helix-core/src/selection.rsnew file+222
@@ -0,0 +1,222 @@
1+//! Selections are the primary editing construct. Even a single cursor is defined as an empty
2+//! single selection range.
3+//!
4+//! All positioning is done via `char` offsets into the buffer.
5+use smallvec::{smallvec, SmallVec};
6+
7+#[inline]
8+fn abs_difference(x: usize, y: usize) -> usize {
9+ if x < y {
10+ y - x
11+ } else {
12+ x - y
13+ }
14+}
15+
16+/// A single selection range. Anchor-inclusive, head-exclusive.
17+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18+pub struct Range {
19+ // TODO: optimize into u32
20+ /// The anchor of the range: the side that doesn't move when extending.
21+ pub anchor: usize,
22+ /// The head of the range, moved when extending.
23+ pub head: usize,
24+}
25+
26+impl Range {
27+ pub fn new(anchor: usize, head: usize) -> Self {
28+ Self { anchor, head }
29+ }
30+
31+ /// Start of the range.
32+ #[inline]
33+ pub fn from(&self) -> usize {
34+ std::cmp::min(self.anchor, self.head)
35+ }
36+
37+ /// End of the range.
38+ #[inline]
39+ pub fn to(&self) -> usize {
40+ std::cmp::max(self.anchor, self.head)
41+ }
42+
43+ /// `true` when head and anchor are at the same position.
44+ #[inline]
45+ pub fn is_empty(&self) -> bool {
46+ self.anchor == self.head
47+ }
48+
49+ /// Check two ranges for overlap.
50+ pub fn overlaps(&self, other: &Self) -> bool {
51+ // cursor overlap is checked differently
52+ if self.is_empty() {
53+ self.from() <= other.to()
54+ } else {
55+ self.from() < other.to()
56+ }
57+ }
58+
59+ // TODO: map
60+
61+ /// Extend the range to cover at least `from` `to`.
62+ pub fn extend(&self, from: usize, to: usize) -> Self {
63+ if from <= self.anchor && to >= self.anchor {
64+ return Range {
65+ anchor: from,
66+ head: to,
67+ };
68+ }
69+
70+ Range {
71+ anchor: self.anchor,
72+ head: if abs_difference(from, self.anchor) > abs_difference(to, self.anchor) {
73+ from
74+ } else {
75+ to
76+ },
77+ }
78+ }
79+
80+ // groupAt
81+}
82+
83+/// A selection consists of one or more selection ranges.
84+pub struct Selection {
85+ // TODO: decide how many ranges to inline SmallVec<[Range; 1]>
86+ ranges: Vec<Range>,
87+ primary_index: usize,
88+}
89+
90+impl Selection {
91+ // map
92+ // eq
93+ pub fn primary(&self) -> Range {
94+ self.ranges[self.primary_index]
95+ }
96+
97+ /// Ensure selection containing only the primary selection.
98+ pub fn as_single(self) -> Self {
99+ if self.ranges.len() == 1 {
100+ self
101+ } else {
102+ Self {
103+ ranges: vec![self.ranges[self.primary_index]],
104+ primary_index: 0,
105+ }
106+ }
107+ }
108+
109+ // add_range // push
110+ // replace_range
111+
112+ /// Constructs a selection holding a single range.
113+ pub fn single(anchor: usize, head: usize) -> Self {
114+ Self {
115+ ranges: vec![Range { anchor, head }],
116+ primary_index: 0,
117+ }
118+ }
119+
120+ pub fn new(ranges: Vec<Range>, primary_index: usize) -> Self {
121+ fn normalize(mut ranges: Vec<Range>, primary_index: usize) -> Selection {
122+ let primary = ranges[primary_index];
123+ ranges.sort_unstable_by_key(|range| range.from());
124+ let mut primary_index = ranges.iter().position(|&range| range == primary).unwrap();
125+
126+ let mut result: Vec<Range> = Vec::new();
127+
128+ // TODO: we could do with one vec by removing elements as we mutate
129+
130+ for (i, range) in ranges.into_iter().enumerate() {
131+ // if previous value exists
132+ if let Some(prev) = result.last_mut() {
133+ // and we overlap it
134+ if range.overlaps(prev) {
135+ let from = prev.from();
136+ let to = std::cmp::max(range.to(), prev.to());
137+
138+ if i <= primary_index {
139+ primary_index -= 1
140+ }
141+
142+ // merge into previous
143+ if range.anchor > range.head {
144+ prev.anchor = to;
145+ prev.head = from;
146+ } else {
147+ prev.anchor = from;
148+ prev.head = to;
149+ }
150+ continue;
151+ }
152+ }
153+
154+ result.push(range)
155+ }
156+
157+ Selection {
158+ ranges: result,
159+ primary_index,
160+ }
161+ }
162+
163+ // TODO: only normalize if needed (any ranges out of order)
164+ normalize(ranges, primary_index)
165+ }
166+}
167+
168+// TODO: checkSelection -> check if valid for doc length
169+
170+#[cfg(test)]
171+mod test {
172+ use super::*;
173+
174+ #[test]
175+ fn test_create_normalizes_and_merges() {
176+ let sel = Selection::new(
177+ vec![
178+ Range::new(10, 12),
179+ Range::new(6, 7),
180+ Range::new(4, 5),
181+ Range::new(3, 4),
182+ Range::new(0, 6),
183+ Range::new(7, 8),
184+ Range::new(9, 13),
185+ Range::new(13, 14),
186+ ],
187+ 0,
188+ );
189+
190+ let res = sel
191+ .ranges
192+ .into_iter()
193+ .map(|range| format!("{}/{}", range.anchor, range.head))
194+ .collect::<Vec<String>>()
195+ .join(",");
196+
197+ assert_eq!(res, "0/6,6/7,7/8,9/13,13/14");
198+ }
199+
200+ #[test]
201+ fn test_create_merges_adjacent_points() {
202+ let sel = Selection::new(
203+ vec![
204+ Range::new(10, 12),
205+ Range::new(12, 12),
206+ Range::new(12, 12),
207+ Range::new(10, 10),
208+ Range::new(8, 10),
209+ ],
210+ 0,
211+ );
212+
213+ let res = sel
214+ .ranges
215+ .into_iter()
216+ .map(|range| format!("{}/{}", range.anchor, range.head))
217+ .collect::<Vec<String>>()
218+ .join(",");
219+
220+ assert_eq!(res, "8/10,10/12");
221+ }
222+}
helix-core/src/state.rsnew file+38
@@ -0,0 +1,38 @@
1+use crate::{Buffer, Selection};
2+
3+/// A state represents the current editor state of a single buffer.
4+pub struct State {
5+ // TODO: maybe doc: ?
6+ buffer: Buffer,
7+ selection: Selection,
8+}
9+
10+impl State {
11+ pub fn new(buffer: Buffer) -> Self {
12+ Self {
13+ buffer,
14+ selection: Selection::single(0, 0),
15+ }
16+ }
17+
18+ // TODO: buf/selection accessors
19+
20+ // update/transact
21+ // replaceSelection (transaction that replaces selection)
22+ // changeByRange
23+ // changes
24+ // slice
25+ //
26+ // getters:
27+ // tabSize
28+ // indentUnit
29+ // languageDataAt()
30+ //
31+ // config:
32+ // indentation
33+ // tabSize
34+ // lineUnit
35+ // syntax
36+ // foldable
37+ // changeFilter/transactionFilter
38+}
helix-core/src/transaction.rsnew file+25
@@ -0,0 +1,25 @@
1+pub struct Change {
2+ from: usize,
3+ to: usize,
4+ insert: Option<String>,
5+}
6+
7+impl Change {
8+ pub fn new(from: usize, to: usize, insert: Option<String>) {
9+ // old_extent, new_extent, insert
10+ }
11+}
12+
13+pub struct Transaction {}
14+
15+// ChangeSpec = Change | ChangeSet | Vec<Change>
16+// ChangeDesc as a ChangeSet without text: can't be applied, cheaper to store.
17+// ChangeSet = ChangeDesc with Text
18+pub struct ChangeSet {
19+ // basically Vec<ChangeDesc> where ChangeDesc = (current len, replacement len?)
20+ // (0, n>0) for insertion, (n>0, 0) for deletion, (>0, >0) for replacement
21+ sections: Vec<(usize, isize)>,
22+}
23+//
24+// trait Transaction
25+// trait StrictTransaction
helix-term/Cargo.toml+4−1
@@ -15,5 +15,8 @@ path = "src/main.rs"
1515# path = "src/line.rs"
1616
1717[dependencies]
18−termwiz = { git = "https://github.com/wez/wezterm", features = ["widgets"] }
18+# termwiz = { git = "https://github.com/wez/wezterm", features = ["widgets"] }
19+termwiz = { path = "../../wezterm/termwiz", default-features = false, features = ["widgets"] }
1920anyhow = "1.0.31"
21+argh = "0.1.3"
22+helix-core = { path = "../helix-core" }
helix-term/src/editor.rsnew file+281
@@ -0,0 +1,281 @@
1+#![allow(unused)]
2+use anyhow::Error;
3+use termwiz::caps::Capabilities;
4+use termwiz::cell::AttributeChange;
5+use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
6+use termwiz::input::*;
7+use termwiz::surface::Change;
8+use termwiz::terminal::{buffered::BufferedTerminal, SystemTerminal, Terminal};
9+use termwiz::widgets::*;
10+
11+use crate::Args;
12+
13+use std::{env, path::PathBuf};
14+
15+use helix_core::Buffer;
16+
17+/// This is a widget for our application
18+pub struct MainScreen {}
19+
20+impl MainScreen {
21+ pub fn new() -> Self {
22+ Self {}
23+ }
24+}
25+
26+impl Widget for MainScreen {
27+ fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
28+ true // handled it all
29+ }
30+
31+ /// Draw ourselves into the surface provided by RenderArgs
32+ fn render(&mut self, args: &mut RenderArgs) {
33+ // args.surface.add_change(Change::ClearScreen(
34+ // ColorAttribute::TrueColorWithPaletteFallback(
35+ // RgbColor::new(0x31, 0x1B, 0x92),
36+ // AnsiColor::Black.into(),
37+ // ),
38+ // ));
39+ // args.surface
40+ // .add_change(Change::Attribute(AttributeChange::Foreground(
41+ // ColorAttribute::TrueColorWithPaletteFallback(
42+ // RgbColor::new(0xB3, 0x88, 0xFF),
43+ // AnsiColor::Purple.into(),
44+ // ),
45+ // )));
46+ }
47+
48+ fn get_size_constraints(&self) -> layout::Constraints {
49+ let mut constraints = layout::Constraints::default();
50+ constraints.child_orientation = layout::ChildOrientation::Vertical;
51+ constraints
52+ }
53+}
54+
55+pub struct BufferComponent<'a> {
56+ text: String,
57+ buffer: &'a mut Buffer,
58+
59+ first_line: usize,
60+}
61+
62+impl<'a> BufferComponent<'a> {
63+ /// Initialize the widget with the input text
64+ pub fn new(buffer: &'a mut Buffer) -> Self {
65+ Self {
66+ buffer,
67+ text: String::new(),
68+
69+ first_line: 0,
70+ }
71+ }
72+}
73+
74+impl<'a> Widget for BufferComponent<'a> {
75+ fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
76+ match event {
77+ WidgetEvent::Input(InputEvent::Key(KeyEvent {
78+ key: KeyCode::Char('k'),
79+ ..
80+ })) => {
81+ self.first_line = self.first_line.saturating_sub(1);
82+ }
83+ WidgetEvent::Input(InputEvent::Key(KeyEvent {
84+ key: KeyCode::Char('j'),
85+ ..
86+ })) => {
87+ self.first_line = self.first_line.saturating_add(1);
88+ }
89+ WidgetEvent::Input(InputEvent::Key(KeyEvent {
90+ key: KeyCode::Enter,
91+ ..
92+ })) => {
93+ self.text.push_str("\r\n");
94+ }
95+ WidgetEvent::Input(InputEvent::Paste(s)) => {
96+ self.text.push_str(&s);
97+ }
98+ _ => {}
99+ }
100+
101+ true // handled it all
102+ }
103+
104+ /// Draw ourselves into the surface provided by RenderArgs
105+ fn render(&mut self, args: &mut RenderArgs) {
106+ args.surface
107+ .add_change(Change::ClearScreen(ColorAttribute::Default));
108+
109+ // args.surface
110+ // .add_change(Change::Attribute(AttributeChange::Foreground(
111+ // ColorAttribute::TrueColorWithPaletteFallback(
112+ // RgbColor::new(0x11, 0x00, 0xFF),
113+ // AnsiColor::Purple.into(),
114+ // ),
115+ // )));
116+ let (_width, height) = args.surface.dimensions();
117+
118+ for line in self.buffer.contents.lines_at(self.first_line).take(height) {
119+ args.surface
120+ .add_change(unsafe { String::from_utf8_unchecked(line.bytes().collect()) });
121+ args.surface.add_change("\r");
122+ }
123+ // args.surface
124+ // .add_change(format!("🤷 surface size is {:?}\r\n", dims));
125+ // args.surface.add_change(self.text.clone());
126+
127+ // Place the cursor at the end of the text.
128+ // A more advanced text editing widget would manage the
129+ // cursor position differently.
130+ *args.cursor = CursorShapeAndPosition {
131+ coords: args.surface.cursor_position().into(),
132+ shape: termwiz::surface::CursorShape::SteadyBar,
133+ ..Default::default()
134+ };
135+ }
136+
137+ fn get_size_constraints(&self) -> layout::Constraints {
138+ let mut c = layout::Constraints::default();
139+ c.set_valign(layout::VerticalAlignment::Top);
140+ c
141+ }
142+}
143+
144+pub struct StatusLine {}
145+
146+impl StatusLine {
147+ pub fn new() -> Self {
148+ StatusLine {}
149+ }
150+}
151+impl Widget for StatusLine {
152+ fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
153+ true
154+ }
155+
156+ fn render(&mut self, args: &mut RenderArgs) {
157+ args.surface.add_change(Change::ClearScreen(
158+ ColorAttribute::TrueColorWithPaletteFallback(
159+ RgbColor::new(0xFF, 0xFF, 0xFF),
160+ AnsiColor::Black.into(),
161+ ),
162+ ));
163+ args.surface
164+ .add_change(Change::Attribute(AttributeChange::Foreground(
165+ ColorAttribute::TrueColorWithPaletteFallback(
166+ RgbColor::new(0x00, 0x00, 0x00),
167+ AnsiColor::Black.into(),
168+ ),
169+ )));
170+
171+ args.surface.add_change(" helix");
172+ }
173+
174+ fn get_size_constraints(&self) -> layout::Constraints {
175+ *layout::Constraints::default()
176+ .set_fixed_height(1)
177+ .set_valign(layout::VerticalAlignment::Bottom)
178+ }
179+}
180+
181+pub struct Editor {
182+ terminal: BufferedTerminal<SystemTerminal>,
183+
184+ buffer: Option<Buffer>,
185+}
186+
187+impl Editor {
188+ pub fn new(mut args: Args) -> Result<Self, Error> {
189+ // Create a terminal
190+ let caps = Capabilities::new_from_env()?;
191+ let mut terminal = BufferedTerminal::new(SystemTerminal::new(caps)?)?;
192+
193+ let mut editor = Editor {
194+ terminal,
195+ buffer: None,
196+ };
197+
198+ if let Some(file) = args.files.pop() {
199+ editor.open(file)?;
200+ }
201+
202+ Ok(editor)
203+ }
204+
205+ pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
206+ let buffer = Buffer::load(path)?;
207+ self.buffer = Some(buffer);
208+ Ok(())
209+ }
210+
211+ pub fn run(&mut self) -> Result<(), Error> {
212+ // Start with an empty string; typing into the app will
213+ // update this string.
214+ let mut typed_text = String::new();
215+
216+ {
217+ let buf = &mut self.terminal;
218+ // Put the terminal in raw mode + alternate screen
219+ buf.terminal().enter_alternate_screen()?;
220+ buf.terminal().set_raw_mode()?;
221+
222+ // Set up the UI
223+ let mut ui = Ui::new();
224+
225+ let root_id = ui.set_root(MainScreen::new());
226+ let buffer_id =
227+ ui.add_child(root_id, BufferComponent::new(self.buffer.as_mut().unwrap()));
228+ // let root_id = ui.set_root(Buffer::new(&mut typed_text));
229+ ui.add_child(root_id, StatusLine::new());
230+ ui.set_focus(buffer_id);
231+
232+ loop {
233+ ui.process_event_queue()?;
234+
235+ // After updating and processing all of the widgets, compose them
236+ // and render them to the screen.
237+ if ui.render_to_screen(buf)? {
238+ // We have more events to process immediately; don't block waiting
239+ // for input below, but jump to the top of the loop to re-run the
240+ // updates.
241+ continue;
242+ }
243+ // Compute an optimized delta to apply to the terminal and display it
244+ buf.flush()?;
245+
246+ // Wait for user input
247+ match buf.terminal().poll_input(None) {
248+ Ok(Some(InputEvent::Resized { rows, cols })) => {
249+ // FIXME: this is working around a bug where we don't realize
250+ // that we should redraw everything on resize in BufferedTerminal.
251+ buf.add_change(Change::ClearScreen(Default::default()));
252+ buf.resize(cols, rows);
253+ }
254+ Ok(Some(input)) => match input {
255+ InputEvent::Key(KeyEvent {
256+ key: KeyCode::Escape,
257+ ..
258+ }) => {
259+ // Quit the app when escape is pressed
260+ break;
261+ }
262+ input @ _ => {
263+ // Feed input into the Ui
264+ ui.queue_event(WidgetEvent::Input(input));
265+ }
266+ },
267+ Ok(None) => {}
268+ Err(e) => {
269+ print!("{:?}\r\n", e);
270+ break;
271+ }
272+ }
273+ }
274+ }
275+
276+ // After we've stopped the full screen raw terminal,
277+ // print out the final edited value of the input text.
278+ println!("The text you entered: {}", typed_text);
279+ Ok(())
280+ }
281+}
helix-term/src/main.rs+13−216
@@ -1,227 +1,24 @@
1−//! This example shows how to make a basic widget that accumulates
2−//! text input and renders it to the screen
3−#![allow(unused)]
4−use anyhow::Error;
5−use termwiz::caps::Capabilities;
6−use termwiz::cell::AttributeChange;
7−use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
8−use termwiz::input::*;
9−use termwiz::surface::Change;
10−use termwiz::terminal::buffered::BufferedTerminal;
11−use termwiz::terminal::{new_terminal, Terminal};
12−use termwiz::widgets::*;
13−
14−/// This is a widget for our application
15−struct MainScreen {}
16−
17−impl MainScreen {
18− pub fn new() -> Self {
19− Self {}
20− }
21−}
22−
23−impl Widget for MainScreen {
24− fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
25− true // handled it all
26− }
27−
28− /// Draw ourselves into the surface provided by RenderArgs
29− fn render(&mut self, args: &mut RenderArgs) {
30− // args.surface.add_change(Change::ClearScreen(
31− // ColorAttribute::TrueColorWithPaletteFallback(
32− // RgbColor::new(0x31, 0x1B, 0x92),
33− // AnsiColor::Black.into(),
34− // ),
35− // ));
36− // args.surface
37− // .add_change(Change::Attribute(AttributeChange::Foreground(
38− // ColorAttribute::TrueColorWithPaletteFallback(
39− // RgbColor::new(0xB3, 0x88, 0xFF),
40− // AnsiColor::Purple.into(),
41− // ),
42− // )));
43− }
44−
45− fn get_size_constraints(&self) -> layout::Constraints {
46− let mut constraints = layout::Constraints::default();
47− constraints.child_orientation = layout::ChildOrientation::Vertical;
48− constraints
49− }
50−}
51−
52−struct Buffer<'a> {
53− /// Holds the input text that we wish the widget to display
54− text: &'a mut String,
55−}
56−
57−impl<'a> Buffer<'a> {
58− /// Initialize the widget with the input text
59− pub fn new(text: &'a mut String) -> Self {
60− Self { text }
61− }
62−}
63−
64−impl<'a> Widget for Buffer<'a> {
65− fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
66− match event {
67− WidgetEvent::Input(InputEvent::Key(KeyEvent {
68− key: KeyCode::Char(c),
69− ..
70− })) => self.text.push(*c),
71− WidgetEvent::Input(InputEvent::Key(KeyEvent {
72− key: KeyCode::Enter,
73− ..
74− })) => {
75− self.text.push_str("\r\n");
76− }
77− WidgetEvent::Input(InputEvent::Paste(s)) => {
78− self.text.push_str(&s);
79− }
80− _ => {}
81− }
82−
83− true // handled it all
84− }
1+mod editor;
852
86− /// Draw ourselves into the surface provided by RenderArgs
87− fn render(&mut self, args: &mut RenderArgs) {
88− args.surface
89− .add_change(Change::ClearScreen(ColorAttribute::Default));
3+use editor::Editor;
904
91− // args.surface
92− // .add_change(Change::Attribute(AttributeChange::Foreground(
93− // ColorAttribute::TrueColorWithPaletteFallback(
94− // RgbColor::new(0x11, 0x00, 0xFF),
95− // AnsiColor::Purple.into(),
96− // ),
97− // )));
98− let dims = args.surface.dimensions();
99− args.surface
100− .add_change(format!("🤷 surface size is {:?}\r\n", dims));
101− args.surface.add_change(self.text.clone());
5+use argh::FromArgs;
6+use std::{env, path::PathBuf};
1027
103− // Place the cursor at the end of the text.
104− // A more advanced text editing widget would manage the
105− // cursor position differently.
106− *args.cursor = CursorShapeAndPosition {
107− coords: args.surface.cursor_position().into(),
108− shape: termwiz::surface::CursorShape::SteadyBar,
109− ..Default::default()
110− };
111− }
112−
113− fn get_size_constraints(&self) -> layout::Constraints {
114− let mut c = layout::Constraints::default();
115− c.set_valign(layout::VerticalAlignment::Top);
116− c
117− }
118−}
119−
120−struct StatusLine {}
121−
122−impl StatusLine {
123− pub fn new() -> Self {
124− StatusLine {}
125− }
126−}
127−impl Widget for StatusLine {
128− fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
129− true
130− }
131−
132− fn render(&mut self, args: &mut RenderArgs) {
133− args.surface.add_change(Change::ClearScreen(
134− ColorAttribute::TrueColorWithPaletteFallback(
135− RgbColor::new(0xFF, 0xFF, 0xFF),
136− AnsiColor::Black.into(),
137− ),
138− ));
139− args.surface
140− .add_change(Change::Attribute(AttributeChange::Foreground(
141− ColorAttribute::TrueColorWithPaletteFallback(
142− RgbColor::new(0x00, 0x00, 0x00),
143− AnsiColor::Black.into(),
144− ),
145− )));
146−
147− args.surface.add_change(" helix");
148− }
8+use anyhow::Error;
1499
150− fn get_size_constraints(&self) -> layout::Constraints {
151− *layout::Constraints::default()
152− .set_fixed_height(1)
153− .set_valign(layout::VerticalAlignment::Bottom)
154− }
10+#[derive(FromArgs)]
11+/// A post-modern text editor.
12+pub struct Args {
13+ #[argh(positional)]
14+ files: Vec<PathBuf>,
15515}
15616
15717fn main() -> Result<(), Error> {
158− // Start with an empty string; typing into the app will
159− // update this string.
160− let mut typed_text = String::new();
161−
162− {
163− // Create a terminal and put it into full screen raw mode
164− let caps = Capabilities::new_from_env()?;
165− let mut buf = BufferedTerminal::new(new_terminal(caps)?)?;
166− buf.terminal().enter_alternate_screen()?;
167− buf.terminal().set_raw_mode()?;
168−
169− // Set up the UI
170− let mut ui = Ui::new();
171−
172− let root_id = ui.set_root(MainScreen::new());
173− let buffer_id = ui.add_child(root_id, Buffer::new(&mut typed_text));
174− // let root_id = ui.set_root(Buffer::new(&mut typed_text));
175− ui.add_child(root_id, StatusLine::new());
176− ui.set_focus(buffer_id);
177−
178− loop {
179− ui.process_event_queue()?;
180−
181− // After updating and processing all of the widgets, compose them
182− // and render them to the screen.
183− if ui.render_to_screen(&mut buf)? {
184− // We have more events to process immediately; don't block waiting
185− // for input below, but jump to the top of the loop to re-run the
186− // updates.
187− continue;
188− }
189− // Compute an optimized delta to apply to the terminal and display it
190− buf.flush()?;
191−
192− // Wait for user input
193− match buf.terminal().poll_input(None) {
194− Ok(Some(InputEvent::Resized { rows, cols })) => {
195− // FIXME: this is working around a bug where we don't realize
196− // that we should redraw everything on resize in BufferedTerminal.
197− buf.add_change(Change::ClearScreen(Default::default()));
198− buf.resize(cols, rows);
199− }
200− Ok(Some(input)) => match input {
201− InputEvent::Key(KeyEvent {
202− key: KeyCode::Escape,
203− ..
204− }) => {
205− // Quit the app when escape is pressed
206− break;
207− }
208− input @ _ => {
209− // Feed input into the Ui
210− ui.queue_event(WidgetEvent::Input(input));
211− }
212− },
213− Ok(None) => {}
214− Err(e) => {
215− print!("{:?}\r\n", e);
216− break;
217− }
218− }
219− }
220− }
18+ let args: Args = argh::from_env();
19+ let mut editor = Editor::new(args)?;
22120
222− // After we've stopped the full screen raw terminal,
223− // print out the final edited value of the input text.
224− println!("The text you entered: {}", typed_text);
21+ editor.run()?;
22522
22623 Ok(())
22724}