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
},
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 = 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
let edit_port = ViewPort::<dyn TerminalView>::new();
let mut editor = string_editor::StringEditor::new(edit_port.inner());
let mut editor = StringEditor::new();
let edit_offset_port = ViewPort::<dyn TerminalView>::new();
let edit_o = GridOffset::new(edit_offset_port.inner());
edit_port.add_observer(edit_o.clone());
compositor.push(
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)))
let edit_view_port = ViewPort::new();
let edit_view =
string_editor::insert_view::StringEditView::new(
editor.get_cursor_port(),
editor.get_data_port().to_sequence(),
edit_view_port.inner()
);
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');
vec_buf.push('c');
vec_buf.insert(1, 'x');
vec_buf.remove(2);
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
// another view of the string, without editor
compositor.push(
editor.get_data_port()
.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 {
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::Right)) => editor.next(),
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>) {
match diff {
VecDiff::Push(_) => {
let mut l = self.cur_len.write().unwrap();
self.cast.notify(&l);
*l += 1;
let l = {
let mut l = self.cur_len.write().unwrap();
*l += 1;
*l
};
self.cast.notify(&(l - 1));
},
VecDiff::Remove(idx) => {
let mut l = self.cur_len.write().unwrap();
*l -= 1;
self.cast.notify_each(*idx .. *l+1);
let l = {
let mut l = self.cur_len.write().unwrap();
*l -= 1;
*l + 1
};
self.cast.notify_each(*idx .. l);
},
VecDiff::Insert{ idx, val: _ } => {
let mut l = self.cur_len.write().unwrap();
*l += 1;
self.cast.notify_each(*idx .. *l);
let l = {
let mut l = self.cur_len.write().unwrap();
*l += 1;
*l
};
self.cast.notify_each(*idx .. l);
},
VecDiff::Update{ idx, val: _ } => {
self.cast.notify(&idx);

View file

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

View file

@ -1,145 +1,276 @@
use {
std::{
sync::{Arc, RwLock},
},
cgmath::Point2,
std::sync::{Arc, RwLock},
crate::{
core::{
ObserverExt,
ObserverBroadcast,
InnerViewPort
},
index::{ImplIndexView},
grid::{GridWindowIterator},
terminal::{TerminalAtom, TerminalStyle, TerminalView},
//vec_buffer::VecBuffer
core::{ViewPort, OuterViewPort, InnerViewPort},
singleton::{SingletonView, SingletonBuffer},
sequence::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 {
state: Arc<RwLock<StringEditorState>>,
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>
data: VecBuffer<char>,
cursor: SingletonBuffer<usize>,
data_port: ViewPort<RwLock<Vec<char>>>,
cursor_port: ViewPort<dyn SingletonView<Item = usize>>
}
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()));
pub fn new() -> Self {
let data_port = ViewPort::new();
let cursor_port = ViewPort::new();
StringEditor {
state,
cast
data: VecBuffer::new(data_port.inner()),
cursor: SingletonBuffer::new(0, cursor_port.inner()),
data_port,
cursor_port
}
}
pub fn next(&mut self) {
let cur = self.state.read().unwrap().cursor;
self.goto(cur + 1);
pub fn get_data_port(&self) -> OuterViewPort<RwLock<Vec<char>>> {
self.data_port.outer()
}
pub fn prev(&mut self) {
let cur = self.state.read().unwrap().cursor;
if cur > 0 {
self.goto(cur - 1);
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) {
let l = self.state.read().unwrap().data.read().unwrap().len();
self.goto(l);
self.cursor.set(self.data.len());
}
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
};
pub fn prev(&mut self) {
let cur = self.cursor.get();
if cur > 0 {
self.cursor.set(cur - 1);
}
}
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 next(&mut self) {
self.goto(self.cursor.get() + 1);
}
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.data.insert(self.cursor.get(), c);
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)));
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 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()
)
}
}
}