refactor string editor
This commit is contained in:
parent
d1c523335b
commit
afaa9d220b
4 changed files with 319 additions and 179 deletions
73
src/main.rs
73
src/main.rs
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(&());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue