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,51 +53,43 @@ 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));
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
// Vec-Buffer
let vec_port = ViewPort::new();
let mut vec_buf = sequence::VecBuffer::<char>::new(vec_port.inner());
// project Vec-Buffer
let vec_term_view = vec_port.outer()
.to_sequence()
.to_index()
.map_key(
|idx: &usize| Point2::<i16>::new(*idx as i16, 0),
|pt: &Point2<i16>| if pt.y == 0 { Some(pt.x as usize) } else { None }
)
.map_item(
|c| TerminalAtom::new(*c, TerminalStyle::fg_color((200, 10, 10)))
); );
compositor.push(vec_term_view); 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_buf.push('a'); //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
vec_buf.push('b'); // another view of the string, without editor
vec_buf.push('c'); compositor.push(
vec_buf.insert(1, 'x'); editor.get_data_port()
vec_buf.remove(2); .to_sequence()
.to_index()
.map_key(
|idx| Point2::new(*idx as i16, 2),
|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)))
)
);
/*\ /*\
<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> <<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
@ -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 mut l = self.cur_len.write().unwrap(); let l = {
self.cast.notify(&l); let mut l = self.cur_len.write().unwrap();
*l += 1; *l += 1;
*l
};
self.cast.notify(&(l - 1));
}, },
VecDiff::Remove(idx) => { VecDiff::Remove(idx) => {
let mut l = self.cur_len.write().unwrap(); let l = {
*l -= 1; let mut l = self.cur_len.write().unwrap();
self.cast.notify_each(*idx .. *l+1); *l -= 1;
*l + 1
};
self.cast.notify_each(*idx .. l);
}, },
VecDiff::Insert{ idx, val: _ } => { VecDiff::Insert{ idx, val: _ } => {
let mut l = self.cur_len.write().unwrap(); let l = {
*l += 1; let mut l = self.cur_len.write().unwrap();
self.cast.notify_each(*idx .. *l); *l += 1;
*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));
SingletonBuffer { port.set_view(Some(Arc::new(SingletonBufferView(value.clone()))));
value,
cast: port.get_broadcast() SingletonBuffer {
} value,
)); 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,145 +1,276 @@
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 {
cursor: usize,
data: Arc<RwLock<Vec<char>>>
}
impl ImplIndexView for StringEditorState {
type Key = Point2<i16>;
type Value = TerminalAtom;
fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> {
let data = self.data.read().unwrap();
if pos.y == 0 {
let i = pos.x as usize;
if i < data.len() + 3 {
return Some(
if i == 0 {
TerminalAtom::new('"', TerminalStyle::fg_color((180,200,130)))
} else if i-1 == self.cursor {
TerminalAtom::new('|', TerminalStyle::fg_color((180,200,130)).add(TerminalStyle::bold(false)))
} else if i-1 == data.len()+1 {
TerminalAtom::new('"', TerminalStyle::fg_color((180,200,130)))
} else {
TerminalAtom::new(
data.get(i as usize - if i <= self.cursor { 1 } else { 2 }).unwrap().clone(),
TerminalStyle::fg_color((80,150,80)).add(TerminalStyle::bold(true))
)
}
)
}
}
None
}
fn area(&self) -> Option<Vec<Point2<i16>>> {
Some(GridWindowIterator::from(
Point2::new(0, 0)
.. Point2::new(self.data.read().unwrap().len() as i16 + 3, 1)).collect())
}
}
pub struct StringEditor { pub struct StringEditor {
state: Arc<RwLock<StringEditorState>>, data: VecBuffer<char>,
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>> cursor: SingletonBuffer<usize>,
data_port: ViewPort<RwLock<Vec<char>>>,
cursor_port: ViewPort<dyn SingletonView<Item = usize>>
} }
impl StringEditor { impl StringEditor {
pub fn new( pub fn new() -> Self {
port: InnerViewPort<dyn TerminalView> let data_port = ViewPort::new();
) -> Self { let cursor_port = ViewPort::new();
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 { StringEditor {
state, data: VecBuffer::new(data_port.inner()),
cast cursor: SingletonBuffer::new(0, cursor_port.inner()),
data_port,
cursor_port
} }
} }
pub fn next(&mut self) { pub fn get_data_port(&self) -> OuterViewPort<RwLock<Vec<char>>> {
let cur = self.state.read().unwrap().cursor; self.data_port.outer()
self.goto(cur + 1);
} }
pub fn prev(&mut self) { pub fn get_cursor_port(&self) -> OuterViewPort<dyn SingletonView<Item = usize>> {
let cur = self.state.read().unwrap().cursor; self.cursor_port.outer()
if cur > 0 { }
self.goto(cur - 1);
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) { pub fn goto_end(&mut self) {
let l = self.state.read().unwrap().data.read().unwrap().len(); self.cursor.set(self.data.len());
self.goto(l);
} }
pub fn goto(&mut self, mut new_idx: usize) { pub fn prev(&mut self) {
let old_idx = { let cur = self.cursor.get();
let mut state = self.state.write().unwrap(); if cur > 0 {
let old_idx = state.cursor.clone(); self.cursor.set(cur - 1);
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( pub fn next(&mut self) {
(std::cmp::min(old_idx, new_idx) ..= std::cmp::max(old_idx, new_idx)) self.goto(self.cursor.get() + 1);
.map(|idx| Point2::new(1+idx as i16, 0))
);
} }
pub fn insert(&mut self, c: char) { pub fn insert(&mut self, c: char) {
self.cast.notify_each({ self.data.insert(self.cursor.get(), c);
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(); self.next();
} }
pub fn delete(&mut self) { pub fn delete(&mut self) {
self.cast.notify_each({ let cur = self.cursor.get();
let state = self.state.write().unwrap(); if cur < self.data.len() {
let mut data = state.data.write().unwrap(); self.data.remove(cur);
}
if state.cursor < data.len() {
data.remove(state.cursor);
}
state.cursor .. data.len()+3
}.map(|idx| Point2::new(1+idx as i16, 0)));
} }
} }
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
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 Value = TerminalAtom;
fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> {
if pos.y == 0 && pos.x >= 0 {
let i = pos.x as usize;
let data =
self.data_obs.as_ref().unwrap()
.read().unwrap()
.data.clone()
.unwrap();
let len = data.len().unwrap();
if i < len+1 {
return Some(
if i < self.cur_pos && i < len {
TerminalAtom::from(data.get(&i).unwrap())
} else if i == self.cur_pos {
TerminalAtom::new('|', TerminalStyle::fg_color((200, 0, 0)))
} else {
TerminalAtom::from(data.get(&(i-1)).unwrap())
}
);
}
}
None
}
fn area(&self) -> Option<Vec<Point2<i16>>> {
let data =
self.data_obs.as_ref().unwrap()
.read().unwrap()
.data.clone()
.unwrap();
let len = data.len()?;
Some(
GridWindowIterator::from(
Point2::new(0, 0) .. Point2::new(len as i16 + 1, 1)
).collect()
)
}
}
}