From e09f3e565622c5a565e09eb457f04a5d5d9b937f Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Sat, 2 Nov 2024 19:19:38 +0100 Subject: [PATCH] add lines exapmle --- Cargo.toml | 2 +- examples/tty-06-lines/Cargo.toml | 19 ++ examples/tty-06-lines/README.md | 8 + examples/tty-06-lines/src/main.rs | 418 ++++++++++++++++++++++++++++++ 4 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 examples/tty-06-lines/Cargo.toml create mode 100644 examples/tty-06-lines/README.md create mode 100644 examples/tty-06-lines/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 38b4295..c3f2dbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,5 @@ members = [ "examples/tty-03-string", "examples/tty-04-posint", "examples/tty-05-dictionary", + "examples/tty-06-lines", ] - diff --git a/examples/tty-06-lines/Cargo.toml b/examples/tty-06-lines/Cargo.toml new file mode 100644 index 0000000..47c4391 --- /dev/null +++ b/examples/tty-06-lines/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tty-06-lines" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +laddertypes = { path = "../../../lib-laddertypes" } +r3vi = { path = "../../../lib-r3vi" } +nested = { path = "../../lib-nested-core" } +nested-tty = { path = "../../lib-nested-tty" } +iterate-text = "0.0.1" +termion = "*" +cgmath = "*" + +[dependencies.async-std] +version = "1.9.0" +features = ["unstable", "attributes"] diff --git a/examples/tty-06-lines/README.md b/examples/tty-06-lines/README.md new file mode 100644 index 0000000..85305aa --- /dev/null +++ b/examples/tty-06-lines/README.md @@ -0,0 +1,8 @@ +# tty-03-string + +Similarly to `tty-02-digit`, a editor is created +but of type . +The contents of the editor can be retrieved by +a morphism from the `EditTree` node. +To demonstrate that, the values are are mapped +to the TTY-display in different form. diff --git a/examples/tty-06-lines/src/main.rs b/examples/tty-06-lines/src/main.rs new file mode 100644 index 0000000..701c26c --- /dev/null +++ b/examples/tty-06-lines/src/main.rs @@ -0,0 +1,418 @@ +extern crate cgmath; +extern crate nested; +extern crate nested_tty; +extern crate r3vi; +extern crate termion; + +use { + cgmath::{Vector2, Point2}, + nested::{ + editors::{ObjCommander, char::CharEditor, list::{ListEditor, ListSegment, ListSegmentSequence}}, + repr_tree::{Context, ReprLeaf, ReprTree, ReprTreeExt, GenericReprTreeMorphism}, + edit_tree::{EditTree, TreeNav, TreeCursor} + }, + nested_tty::{ + DisplaySegment, TTYApplication, + TerminalCompositor, TerminalStyle, TerminalView, + TerminalAtom, TerminalEvent, + edit_tree::cursor_widget::TreeNavExt, + TerminalProjections + }, + r3vi::{ + buffer::{singleton::*, vec::*}, + view::{ObserverBroadcast, Observer, ObserverExt, View, ViewPort, OuterViewPort, port::UpdateTask, list::*, sequence::*, singleton::*, index::*, grid::*}, + projection::projection_helper::ProjectionHelper + }, + std::sync::{Arc, RwLock}, +}; + +#[derive(Clone)] +struct LineDiagnostic { + range: (u32, u32), + msg: String +} + +struct LineView { + line_num: Arc< dyn SingletonView >, + segments: Arc< dyn SequenceView >, + diagnostics: Arc< dyn SequenceView >, + + diag_buf: VecBuffer< LineDiagnostic >, + + cast: Arc>>, + out_port: OuterViewPort, + proj_helper: ProjectionHelper +} + +impl View for LineView { + type Msg = IndexArea>; +} + +impl IndexView> for LineView { + type Item = TerminalAtom; + + fn get(&self, pos: &Point2) -> Option< TerminalAtom > { + let xoff = 6; + + let mut pos = pos.clone(); + pos.x -= xoff; + if pos.y == 0 { + // main line + if pos.x >= 0 { + match self.segments.get(&(pos.x as usize)) { + Some(ListSegment::InsertCursor) => { + Some(TerminalAtom::new('|', TerminalStyle::fg_color((220, 80, 40)))) + } + Some(ListSegment::Item { editor, cur_dist }) => { + if let Some(e) = editor.get_edit::() { + let c = e.read().unwrap().get(); + let mut style = TerminalStyle::default(); + let i = if cur_dist >= 0 { pos.x as u32 } else { pos.x as u32 - 1 }; + for d in self.diagnostics.iter() { + if d.range.0 <= i && d.range.1 > i { + style = TerminalStyle::bg_color((110, 80, 30)) + } + } + Some(TerminalAtom::new(c, style)) + } else { + Some(TerminalAtom::from('?')) + } + } + None => None + } + } else if pos.x <= -2 { + // line number + let mut n = self.line_num.get(); + let digit_idx = -(pos.x+2); + for _ in 0..digit_idx { n /= 10; } + Some( + TerminalAtom::new( + char::from_digit((n % 10) as u32, 10).unwrap_or('?'), + TerminalStyle::fg_color((120,120,120)) + ) + ) + } else { + None + } + } else if pos.y > 0 { + // optional diagnostic message + let diag_idx = pos.y as usize - 1; + if let Some(diag) = self.diagnostics.get(&diag_idx) { + if let Some(c) = diag.msg.chars().nth( pos.x as usize ) { + Some(TerminalAtom::new(c, + TerminalStyle::bg_color((20,20,0)) + .add(TerminalStyle::fg_color((220, 40, 40))) + )) + } else { + None + } + } else { + None + } + } else { + None + } + } + + fn area(&self) -> IndexArea> { + let xoff = 6; + let mut n = self.line_num.get(); + let mut n_digits = 0 as i16; + while n > 0 { n_digits += 1; n /= 10; } + let diag_len = self.diagnostics.iter().map(|d| d.msg.chars().count() as i16).max().unwrap_or(0); + IndexArea::Range( + Point2::new( xoff -1-n_digits , 0) ..= + Point2::new( + xoff+ + i16::max( + self.segments.len().unwrap_or(i16::MAX as usize) as i16, + diag_len + ), + self.diagnostics.len().unwrap_or(i16::MAX as usize) as i16, + ) + ) + } +} + +impl LineView { + pub fn new( + n: u64, + le: &ListEditor, + ) -> Arc> { + let line_num_buf = SingletonBuffer::new(n); + let diag_buf = VecBuffer::new(); + let seg_seq = ListSegmentSequence::new(le.get_cursor_port(), le.get_data_port()) + .read().unwrap().get_view(); + + let out_port = ViewPort::new(); + let mut proj_helper = ProjectionHelper::new(out_port.update_hooks.clone()); + + let lv = Arc::new(RwLock::new(LineView{ + line_num: proj_helper.new_singleton_arg(0, line_num_buf.get_port(), + |s: &mut LineView, _msg|{ + s.cast.write().unwrap() + .notify(&IndexArea::Range( + (Point2::new(-100, 0) ..= Point2::new(0, 0)) + )); + }), + segments: proj_helper.new_sequence_arg(1, seg_seq, + |s: &mut LineView, idx| { + s.cast.write().unwrap() + .notify(&IndexArea::Range( + (Point2::new(0, *idx as i16 - 1) ..= Point2::new(100, *idx as i16)) + )); + }), + diagnostics: proj_helper.new_sequence_arg(2, diag_buf.get_port().to_sequence(), + |s: &mut LineView, idx| { + s.cast.write().unwrap() + .notify(&IndexArea::Range( + (Point2::new(-100, 1+*idx as i16) ..= Point2::new(100, 1+*idx as i16)) + )); + }), + + diag_buf, + + cast: out_port.inner().get_broadcast(), + proj_helper, + out_port: out_port.outer() + })); + + lv.write().unwrap().proj_helper.set_proj(&lv); + out_port.inner().set_view(Some(lv.clone())); + + lv + } + + pub fn get_port(&self) -> OuterViewPort { + self.out_port.clone() + } +} + +struct LinesEditor { + ctx: Arc>, + edit: Arc> +} + +impl LinesEditor { + pub fn new(ctx: Arc>) -> Self { + let typ = Context::parse(&ctx, ""); + let depth_port = SingletonBuffer::::new(0).get_port(); + + let list_edit = ListEditor::new(ctx.clone(), typ); + + let line_to_edittree = GenericReprTreeMorphism::new( + Context::parse(&ctx, "Line ~ "), + Context::parse(&ctx, "Line ~ EditTree"), + |rt, σ| { + eprintln!("LINE EDITOR CONSTRUCT"); + } + ); + ctx.write().unwrap().morphisms.add_morphism( line_to_edittree ); + + let lines_segments = nested::editors::list::ListSegmentSequence::new( + list_edit.get_cursor_port(), + list_edit.get_data_port() + ).read().unwrap().get_view(); + let lines_view = lines_segments + .map({ + let ctx = ctx.clone(); + move |segment| match segment { + nested::editors::list::ListSegment::InsertCursor => { + nested_tty::make_label("..... |") + .with_fg_color((220, 80, 40)) + }, + nested::editors::list::ListSegment::Item { editor, cur_dist } => { + if *cur_dist == 0 { + editor.display_view() + .map_item(|x,a| a + .add_style_back(TerminalStyle::bg_color((50, 50, 50))) + ) + } else { + editor.display_view() + } + } + } + }) + .to_grid_vertical() + .flatten(); + + let mut list_edit = list_edit.into_node( depth_port ); + nested_tty::editors::list::PTYListController::for_node( &mut list_edit, Some('\n'), None ); + list_edit.disp.view + .write().unwrap() + .insert_branch(ReprTree::from_view( + Context::parse(&ctx, "TerminalView"), + lines_view + )); + + LinesEditor { + // lines, + edit: Arc::new(RwLock::new(list_edit)), + ctx + } + } + + pub fn add_line(&mut self, line_value: &str) { + let n = self.edit.write().unwrap() + .get_edit::< ListEditor >().unwrap() + .read().unwrap() + .data.len() as u64; + + let line = + self.make_line(line_value) + .descend(Context::parse(&self.ctx, "EditTree")).unwrap() + .edittree(&self.ctx).get(); + + let le = line.read().unwrap().get_edit::().unwrap(); + le.write().unwrap().goto(TreeCursor::none()); + + let lvport = LineView::new( n, &*le.read().unwrap() ).read().unwrap().get_port(); + line.write().unwrap().disp.view + .insert_leaf( + Context::parse(&self.ctx, "TerminalView"), + ReprLeaf::from_view( lvport ) + ); + + self.edit.write().unwrap() + .get_edit::< ListEditor >().unwrap() + .write().unwrap() + .data + .push(line); + } + + pub fn make_line(&self, line_value: &str) -> Arc> { + let ctx = &self.ctx; + let mut rt_line = ReprTree::from_str( + Context::parse(&ctx, "~"), + line_value + ); + + ctx.read().unwrap().apply_morphism( + &rt_line, + &laddertypes::MorphismType { + src_type: Context::parse(&ctx, ""), + dst_type: Context::parse(&ctx, " ~ EditTree") + } + ); + + // .. avoid cycle of projections.. + rt_line.write().unwrap().detach(&ctx); + + ctx.read().unwrap().apply_morphism( + &rt_line, + &laddertypes::MorphismType { + src_type: Context::parse(&ctx, "~EditTree"), + dst_type: Context::parse(&ctx, "") + } + ); + ctx.read().unwrap().apply_morphism( + &rt_line, + &laddertypes::MorphismType { + src_type: Context::parse(&ctx, ""), + dst_type: Context::parse(&ctx, "~") + } + ); + + rt_line + } + + pub fn add_diagnostic(&mut self, line_num: usize, diag: LineDiagnostic) { + /* + let mut line = self.edit.write().unwrap() + .get_edit::< ListEditor >().expect("cant get list edit") + .write().unwrap() + .data + .get(line_num) + .get_edit::< LineEditor >().expect("cant get line edit"); + + line.write().unwrap().diag_buf.push(diag); + */ + } +/* + pub fn get_lines(&self) -> Vec { + // TODO + let lines = self.edit.read().unwrap().get_edit::< ListEditor >().expect("cant get list edit") + .read().unwrap().data + .clone().into_inner() + .read().unwrap() + .iter() + .map(|line_edittree_arc| { + let line_edittree = line_edittree_arc.read().unwrap(); + line_edittree. + }) + } + + pub fn get(&self) -> String { + self.get_lines().iter() + .map(|l| + l.chars().chain(std::iter::once('\n')) + ).flatten() + .collect() + } +*/ +} + + +#[async_std::main] +async fn main() { + /* setup context + */ + let ctx = Arc::new(RwLock::new(Context::new())); + nested::editors::char::init_ctx( ctx.clone() ); + nested::editors::digit::init_ctx( ctx.clone() ); + nested::editors::integer::init_ctx( ctx.clone() ); + nested::editors::list::init_ctx( ctx.clone() ); + nested_tty::setup_edittree_hook(&ctx); + + let args: Vec = std::env::args().collect(); + let path = String::from(args.get(1).expect("no filename given")); + let mut lines_edit = LinesEditor::new(ctx.clone()); + + let iter_lines = iterate_text::file::lines::IterateFileLines::new(path.clone()); + for line in iter_lines { + let mut sanitized_line = String::new(); + for c in line.chars() { + if c == '\t' { + for _ in 0..4 { sanitized_line.push(' '); } + } else if c != '\n' { + sanitized_line.push(c); + } + } + lines_edit.add_line(&sanitized_line); + } + + lines_edit.edit.write().unwrap().goto(TreeCursor::home()); + lines_edit.add_diagnostic( 5, LineDiagnostic{ range: (0, 10), msg: "test diagnostic".into() } ); + + /* setup terminal + */ + let app = TTYApplication::new({ + let edittree = lines_edit.edit.clone(); + + /* event handler + */ + let ctx = ctx.clone(); + move |ev| { + edittree.write().unwrap().send_cmd_obj(ev.to_repr_tree(&ctx)); + } + }); + + /* Setup the compositor to serve as root-view + * by routing it to the `app.port` Viewport, + * so it will be displayed on TTY-output. + */ + let compositor = TerminalCompositor::new(app.port.inner()); + + /* Now add some views to our compositor + */ + { + let mut comp = compositor.write().unwrap(); + let edit = lines_edit.edit.read().unwrap(); + comp.push(edit.get_cursor_widget()); + comp.push(edit.display_view().offset(Vector2::new(0, 1))); + } + + /* write the changes in the view of `term_port` to the terminal + */ + app.show().await.expect("output error!"); +}