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 <List Char>.
+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<Item = u64> >,
+    segments: Arc< dyn SequenceView<Item = ListSegment> >,
+    diagnostics: Arc< dyn SequenceView<Item = LineDiagnostic> >,
+
+    diag_buf: VecBuffer< LineDiagnostic >,
+
+    cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>,
+    out_port: OuterViewPort<dyn TerminalView>,
+    proj_helper: ProjectionHelper<usize, Self>
+}
+
+impl View for LineView {
+    type Msg = IndexArea<Point2<i16>>;
+}
+
+impl IndexView<Point2<i16>> for LineView {
+    type Item = TerminalAtom;
+
+    fn get(&self, pos: &Point2<i16>) -> 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::<CharEditor>() {
+                            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<Point2<i16>> {
+        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<RwLock<Self>> {
+        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<dyn TerminalView> {
+        self.out_port.clone()
+    }
+}
+
+struct LinesEditor {
+    ctx: Arc<RwLock<Context>>,
+    edit: Arc<RwLock<EditTree>>
+}
+
+impl LinesEditor {
+    pub fn new(ctx: Arc<RwLock<Context>>) -> Self {
+        let typ = Context::parse(&ctx, "<List Char>");
+        let depth_port = SingletonBuffer::<usize>::new(0).get_port();
+
+        let list_edit = ListEditor::new(ctx.clone(), typ);
+
+        let line_to_edittree = GenericReprTreeMorphism::new(
+            Context::parse(&ctx, "Line ~ <Seq~List Char>"),
+            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::<ListEditor>().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<RwLock<ReprTree>> {
+        let ctx = &self.ctx;
+        let mut rt_line = ReprTree::from_str(
+            Context::parse(&ctx, "<List Char>~<Vec Char>"),
+            line_value
+        );
+
+        ctx.read().unwrap().apply_morphism(
+            &rt_line,
+            &laddertypes::MorphismType {
+                src_type: Context::parse(&ctx, "<List~Vec Char>"),
+                dst_type: Context::parse(&ctx, "<List Char> ~ 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, "<List Char>~EditTree"),
+                dst_type: Context::parse(&ctx, "<List Char>")
+            }
+        );
+        ctx.read().unwrap().apply_morphism(
+            &rt_line,
+            &laddertypes::MorphismType {
+                src_type: Context::parse(&ctx, "<List Char>"),
+                dst_type: Context::parse(&ctx, "<List Char>~<Vec Char>")
+            }
+        );
+
+        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<String> {
+        // 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<String> = 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!");
+}