refactor string editor

This commit is contained in:
Michael Sippel 2021-01-18 17:00:53 +01:00
parent d1c523335b
commit afaa9d220b
Signed by: senvas
GPG key ID: F96CF119C34B64A6
4 changed files with 319 additions and 179 deletions

View file

@ -28,7 +28,8 @@ use {
TerminalCompositor TerminalCompositor
}, },
grid::{GridOffset, GridWindowIterator}, grid::{GridOffset, GridWindowIterator},
singleton::{SingletonView, SingletonBuffer} singleton::{SingletonView, SingletonBuffer},
string_editor::{StringEditor}
} }
}; };
@ -52,52 +53,44 @@ async fn main() {
\*/ \*/
let window_size_port = ViewPort::new(); let window_size_port = ViewPort::new();
let window_size = SingletonBuffer::new(Vector2::new(0, 0), window_size_port.inner()); let mut window_size = SingletonBuffer::new(Vector2::new(0, 0), window_size_port.inner());
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
// string editor // string editor
let edit_port = ViewPort::<dyn TerminalView>::new(); let mut editor = StringEditor::new();
let mut editor = string_editor::StringEditor::new(edit_port.inner());
let edit_offset_port = ViewPort::<dyn TerminalView>::new(); let edit_view_port = ViewPort::new();
let edit_o = GridOffset::new(edit_offset_port.inner()); let edit_view =
string_editor::insert_view::StringEditView::new(
edit_port.add_observer(edit_o.clone()); editor.get_cursor_port(),
editor.get_data_port().to_sequence(),
compositor.push( edit_view_port.inner()
edit_offset_port
.into_outer()
// add a nice black background
.map_item(|a| a.add_style_back(TerminalStyle::bg_color((0,0,0))))
); );
edit_o.write().unwrap().set_offset(Vector2::new(40, 4)); compositor.push(
edit_view_port.outer()
.map_item(
|_pos, atom| atom.add_style_back(
TerminalStyle::fg_color((200,200,200))
.add(TerminalStyle::bg_color((0,0,0)))
.add(TerminalStyle::bold(true)))
)
);
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
// Vec-Buffer // another view of the string, without editor
let vec_port = ViewPort::new(); compositor.push(
let mut vec_buf = sequence::VecBuffer::<char>::new(vec_port.inner()); editor.get_data_port()
// project Vec-Buffer
let vec_term_view = vec_port.outer()
.to_sequence() .to_sequence()
.to_index() .to_index()
.map_key( .map_key(
|idx: &usize| Point2::<i16>::new(*idx as i16, 0), |idx| Point2::new(*idx as i16, 2),
|pt: &Point2<i16>| if pt.y == 0 { Some(pt.x as usize) } else { None } |pt| if pt.y == 2 { Some(pt.x as usize) } else { None }
).map_item(
|_key, c| TerminalAtom::new(*c, TerminalStyle::fg_color((0, 200, 0)))
) )
.map_item(
|c| TerminalAtom::new(*c, TerminalStyle::fg_color((200, 10, 10)))
); );
compositor.push(vec_term_view);
vec_buf.push('a');
vec_buf.push('b');
vec_buf.push('c');
vec_buf.insert(1, 'x');
vec_buf.remove(2);
/*\ /*\
<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> <<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
Event Loop Event Loop
@ -105,7 +98,7 @@ async fn main() {
\*/ \*/
loop { loop {
match term.next_event().await { match term.next_event().await {
TerminalEvent::Resize(size) => window_size.write().unwrap().set(size), TerminalEvent::Resize(size) => window_size.set(size),
TerminalEvent::Input(Event::Key(Key::Left)) => editor.prev(), TerminalEvent::Input(Event::Key(Key::Left)) => editor.prev(),
TerminalEvent::Input(Event::Key(Key::Right)) => editor.next(), TerminalEvent::Input(Event::Key(Key::Right)) => editor.next(),
TerminalEvent::Input(Event::Key(Key::Home)) => editor.goto(0), TerminalEvent::Input(Event::Key(Key::Home)) => editor.goto(0),

View file

@ -70,19 +70,28 @@ where T: Clone + Send + Sync + 'static {
fn notify(&self, diff: &VecDiff<T>) { fn notify(&self, diff: &VecDiff<T>) {
match diff { match diff {
VecDiff::Push(_) => { VecDiff::Push(_) => {
let l = {
let mut l = self.cur_len.write().unwrap(); let mut l = self.cur_len.write().unwrap();
self.cast.notify(&l);
*l += 1; *l += 1;
*l
};
self.cast.notify(&(l - 1));
}, },
VecDiff::Remove(idx) => { VecDiff::Remove(idx) => {
let l = {
let mut l = self.cur_len.write().unwrap(); let mut l = self.cur_len.write().unwrap();
*l -= 1; *l -= 1;
self.cast.notify_each(*idx .. *l+1); *l + 1
};
self.cast.notify_each(*idx .. l);
}, },
VecDiff::Insert{ idx, val: _ } => { VecDiff::Insert{ idx, val: _ } => {
let l = {
let mut l = self.cur_len.write().unwrap(); let mut l = self.cur_len.write().unwrap();
*l += 1; *l += 1;
self.cast.notify_each(*idx .. *l); *l
};
self.cast.notify_each(*idx .. l);
}, },
VecDiff::Update{ idx, val: _ } => { VecDiff::Update{ idx, val: _ } => {
self.cast.notify(&idx); self.cast.notify(&idx);

View file

@ -13,45 +13,52 @@ use {
} }
}; };
pub struct SingletonBuffer<T> pub struct SingletonBufferView<T: Clone + Eq + Send + Sync + 'static>(Arc<RwLock<T>>);
where T: Clone + Eq + Send + Sync + 'static {
value: T,
cast: Arc<RwLock<ObserverBroadcast<dyn SingletonView<Item = T>>>>
}
impl<T> View for SingletonBuffer<T> impl<T> View for SingletonBufferView<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Eq + Send + Sync + 'static {
type Msg = (); type Msg = ();
} }
impl<T> SingletonView for SingletonBuffer<T> impl<T> SingletonView for SingletonBufferView<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Eq + Send + Sync + 'static {
type Item = T; type Item = T;
fn get(&self) -> Self::Item { fn get(&self) -> Self::Item {
self.value.clone() self.0.read().unwrap().clone()
} }
} }
pub struct SingletonBuffer<T>
where T: Clone + Eq + Send + Sync + 'static {
value: Arc<RwLock<T>>,
cast: Arc<RwLock<ObserverBroadcast<dyn SingletonView<Item = T>>>>
}
impl<T> SingletonBuffer<T> impl<T> SingletonBuffer<T>
where T: Clone + Eq + Send + Sync + 'static { where T: Clone + Eq + Send + Sync + 'static {
pub fn new( pub fn new(
value: T, value: T,
port: InnerViewPort<dyn SingletonView<Item = T>> port: InnerViewPort<dyn SingletonView<Item = T>>
) -> Arc<RwLock<Self>> { ) -> Self {
let buf = Arc::new(RwLock::new( let value = Arc::new(RwLock::new(value));
port.set_view(Some(Arc::new(SingletonBufferView(value.clone()))));
SingletonBuffer { SingletonBuffer {
value, value,
cast: port.get_broadcast() cast: port.get_broadcast()
} }
)); }
port.set_view(Some(buf.clone()));
buf pub fn get(&self) -> T {
self.value.read().unwrap().clone()
} }
pub fn set(&mut self, new_value: T) { pub fn set(&mut self, new_value: T) {
if self.value != new_value { let mut v = self.value.write().unwrap();
self.value = new_value; if *v != new_value {
*v = new_value;
drop(v);
self.cast.notify(&()); self.cast.notify(&());
} }
} }

View file

@ -1,53 +1,255 @@
use { use {
std::{ std::sync::{Arc, RwLock},
sync::{Arc, RwLock},
},
cgmath::Point2,
crate::{ crate::{
core::{ core::{ViewPort, OuterViewPort, InnerViewPort},
ObserverExt, singleton::{SingletonView, SingletonBuffer},
ObserverBroadcast, sequence::VecBuffer
InnerViewPort
},
index::{ImplIndexView},
grid::{GridWindowIterator},
terminal::{TerminalAtom, TerminalStyle, TerminalView},
//vec_buffer::VecBuffer
} }
}; };
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
pub struct StringEditorState { pub struct StringEditor {
cursor: usize, data: VecBuffer<char>,
data: Arc<RwLock<Vec<char>>> cursor: SingletonBuffer<usize>,
data_port: ViewPort<RwLock<Vec<char>>>,
cursor_port: ViewPort<dyn SingletonView<Item = usize>>
} }
impl ImplIndexView for StringEditorState { impl StringEditor {
pub fn new() -> Self {
let data_port = ViewPort::new();
let cursor_port = ViewPort::new();
StringEditor {
data: VecBuffer::new(data_port.inner()),
cursor: SingletonBuffer::new(0, cursor_port.inner()),
data_port,
cursor_port
}
}
pub fn get_data_port(&self) -> OuterViewPort<RwLock<Vec<char>>> {
self.data_port.outer()
}
pub fn get_cursor_port(&self) -> OuterViewPort<dyn SingletonView<Item = usize>> {
self.cursor_port.outer()
}
pub fn goto(&mut self, new_pos: usize) {
if new_pos <= self.data.len() {
self.cursor.set(new_pos);
}
}
pub fn goto_end(&mut self) {
self.cursor.set(self.data.len());
}
pub fn prev(&mut self) {
let cur = self.cursor.get();
if cur > 0 {
self.cursor.set(cur - 1);
}
}
pub fn next(&mut self) {
self.goto(self.cursor.get() + 1);
}
pub fn insert(&mut self, c: char) {
self.data.insert(self.cursor.get(), c);
self.next();
}
pub fn delete(&mut self) {
let cur = self.cursor.get();
if cur < self.data.len() {
self.data.remove(cur);
}
}
}
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
pub mod insert_view {
use {
std::sync::{Arc, RwLock, Weak},
cgmath::Point2,
crate::{
core::{Observer, ObserverExt, ObserverBroadcast, OuterViewPort, InnerViewPort},
singleton::SingletonView,
sequence::SequenceView,
index::ImplIndexView,
grid::GridWindowIterator,
terminal::{TerminalAtom, TerminalStyle, TerminalView}
}
};
struct CursorObserver {
cursor: Option<Arc<dyn SingletonView<Item = usize>>>,
edit: Weak<RwLock<StringEditView>>
}
impl Observer<dyn SingletonView<Item = usize>> for CursorObserver {
fn reset(&mut self, new_cursor: Option<Arc<dyn SingletonView<Item = usize>>>) {
self.cursor = new_cursor;
if let Some(cursor) = self.cursor.as_ref() {
self.edit
.upgrade().unwrap()
.write().unwrap()
.update_cursor( cursor.get() );
}
}
fn notify(&self, _msg: &()) {
if let Some(cursor) = self.cursor.as_ref() {
self.edit
.upgrade().unwrap()
.write().unwrap()
.update_cursor( cursor.get() );
}
}
}
struct DataObserver {
data: Option<Arc<dyn SequenceView<Item = char>>>,
edit: Weak<RwLock<StringEditView>>
}
impl Observer<dyn SequenceView<Item = char>> for DataObserver {
fn reset(&mut self, new_data: Option<Arc<dyn SequenceView<Item = char>>>) {
let old_len =
if let Some(data) = self.data.as_ref() {
data.len().unwrap_or(0)
} else {
0
};
self.data = new_data;
let new_len =
if let Some(data) = self.data.as_ref() {
data.len().unwrap_or(0)
} else {
0
};
self.edit
.upgrade().unwrap()
.write().unwrap()
.reset_data( std::cmp::max(old_len, new_len) );
}
fn notify(&self, pos: &usize) {
self.edit
.upgrade().unwrap()
.write().unwrap()
.update_data( *pos );
}
}
pub struct StringEditView {
data_obs: Option<Arc<RwLock<DataObserver>>>,
cursor_obs: Option<Arc<RwLock<CursorObserver>>>,
cur_pos: usize,
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>
}
impl StringEditView {
pub fn new(
cursor_port: OuterViewPort<dyn SingletonView<Item = usize>>,
data_port: OuterViewPort<dyn SequenceView<Item = char>>,
out_port: InnerViewPort<dyn TerminalView>
) -> Arc<RwLock<Self>> {
let edit_view = Arc::new(RwLock::new(
StringEditView {
data_obs: None,
cursor_obs: None,
cur_pos: 0,
cast: out_port.get_broadcast()
}
));
let data_obs = Arc::new(RwLock::new(
DataObserver {
data: None,
edit: Arc::downgrade(&edit_view)
}
));
edit_view.write().unwrap().data_obs = Some(data_obs.clone());
data_port.add_observer(data_obs);
let cursor_obs = Arc::new(RwLock::new(
CursorObserver {
cursor: None,
edit: Arc::downgrade(&edit_view)
}
));
edit_view.write().unwrap().cursor_obs = Some(cursor_obs.clone());
cursor_port.add_observer(cursor_obs);
out_port.set_view(Some(edit_view.clone()));
edit_view
}
fn reset_data(&mut self, max_len: usize) {
self.cast.notify_each(GridWindowIterator::from(
Point2::new(0, 0) .. Point2::new(max_len as i16 + 1, 1)
));
}
fn update_data(&mut self, pos: usize) {
self.cast.notify(
&Point2::new(
if pos < self.cur_pos {
pos
} else {
pos + 1
} as i16,
0
)
);
}
fn update_cursor(&mut self, new_pos: usize) {
let old_pos = self.cur_pos;
self.cur_pos = new_pos;
self.cast.notify_each(GridWindowIterator::from(
Point2::new(std::cmp::min(old_pos,new_pos) as i16, 0) .. Point2::new(std::cmp::max(old_pos,new_pos) as i16 + 1, 1)
));
}
}
impl ImplIndexView for StringEditView {
type Key = Point2<i16>; type Key = Point2<i16>;
type Value = TerminalAtom; type Value = TerminalAtom;
fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> { fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> {
let data = self.data.read().unwrap(); if pos.y == 0 && pos.x >= 0 {
if pos.y == 0 {
let i = pos.x as usize; let i = pos.x as usize;
if i < data.len() + 3 { let data =
self.data_obs.as_ref().unwrap()
.read().unwrap()
.data.clone()
.unwrap();
let len = data.len().unwrap();
if i < len+1 {
return Some( return Some(
if i == 0 { if i < self.cur_pos && i < len {
TerminalAtom::new('"', TerminalStyle::fg_color((180,200,130))) TerminalAtom::from(data.get(&i).unwrap())
} else if i-1 == self.cursor { } else if i == self.cur_pos {
TerminalAtom::new('|', TerminalStyle::fg_color((180,200,130)).add(TerminalStyle::bold(false))) TerminalAtom::new('|', TerminalStyle::fg_color((200, 0, 0)))
} else if i-1 == data.len()+1 {
TerminalAtom::new('"', TerminalStyle::fg_color((180,200,130)))
} else { } else {
TerminalAtom::new( TerminalAtom::from(data.get(&(i-1)).unwrap())
data.get(i as usize - if i <= self.cursor { 1 } else { 2 }).unwrap().clone(),
TerminalStyle::fg_color((80,150,80)).add(TerminalStyle::bold(true))
)
} }
) );
} }
} }
@ -55,91 +257,20 @@ impl ImplIndexView for StringEditorState {
} }
fn area(&self) -> Option<Vec<Point2<i16>>> { fn area(&self) -> Option<Vec<Point2<i16>>> {
Some(GridWindowIterator::from( let data =
Point2::new(0, 0) self.data_obs.as_ref().unwrap()
.. Point2::new(self.data.read().unwrap().len() as i16 + 3, 1)).collect()) .read().unwrap()
.data.clone()
.unwrap();
let len = data.len()?;
Some(
GridWindowIterator::from(
Point2::new(0, 0) .. Point2::new(len as i16 + 1, 1)
).collect()
)
}
} }
} }
pub struct StringEditor {
state: Arc<RwLock<StringEditorState>>,
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>
}
impl StringEditor {
pub fn new(
port: InnerViewPort<dyn TerminalView>
) -> Self {
let state = Arc::new(RwLock::new(StringEditorState{
cursor: 7,
data: Arc::new(RwLock::new("edit me".chars().collect()))
}));
let cast = port.set_view(Some(state.clone()));
StringEditor {
state,
cast
}
}
pub fn next(&mut self) {
let cur = self.state.read().unwrap().cursor;
self.goto(cur + 1);
}
pub fn prev(&mut self) {
let cur = self.state.read().unwrap().cursor;
if cur > 0 {
self.goto(cur - 1);
}
}
pub fn goto_end(&mut self) {
let l = self.state.read().unwrap().data.read().unwrap().len();
self.goto(l);
}
pub fn goto(&mut self, mut new_idx: usize) {
let old_idx = {
let mut state = self.state.write().unwrap();
let old_idx = state.cursor.clone();
let len = state.data.read().unwrap().len();
new_idx = std::cmp::min(new_idx, len);
state.cursor = new_idx;
old_idx
};
self.cast.notify_each(
(std::cmp::min(old_idx, new_idx) ..= std::cmp::max(old_idx, new_idx))
.map(|idx| Point2::new(1+idx as i16, 0))
);
}
pub fn insert(&mut self, c: char) {
self.cast.notify_each({
let state = self.state.write().unwrap();
let mut data = state.data.write().unwrap();
data.insert(state.cursor, c);
state.cursor .. data.len()+2
}.map(|idx| Point2::new(1+idx as i16, 0)));
self.next();
}
pub fn delete(&mut self) {
self.cast.notify_each({
let state = self.state.write().unwrap();
let mut data = state.data.write().unwrap();
if state.cursor < data.len() {
data.remove(state.cursor);
}
state.cursor .. data.len()+3
}.map(|idx| Point2::new(1+idx as i16, 0)));
}
}