diff --git a/abacus-core/src/dataframe.rs b/abacus-core/src/dataframe.rs index cae4c7d..12b4ef5 100644 --- a/abacus-core/src/dataframe.rs +++ b/abacus-core/src/dataframe.rs @@ -25,8 +25,14 @@ pub fn setup_engine(engine: &mut rhai::Engine) { engine.register_fn("to_series", script_functions::to_series_unnamed); engine.register_fn("series", script_functions::series_unnamed); engine.register_fn("head", Series::s_head); + engine.register_fn("head", Series::s_head_len); + engine.register_fn("tail", Series::s_tail); + engine.register_fn("tail", Series::s_tail_len); engine.register_fn("sort", Series::s_sort); engine.register_fn("sum", Series::s_sum); + engine.register_fn("mean", Series::s_mean); + engine.register_fn("min", Series::s_min); + engine.register_fn("max", Series::s_max); engine.register_fn("add", Series::s_op_add); engine.register_fn("+", Series::add); @@ -67,6 +73,7 @@ pub fn setup_engine(engine: &mut rhai::Engine) { engine.register_fn("min", script_functions::min); engine.register_fn("max", script_functions::max); engine.register_fn("first", script_functions::first); + engine.register_fn("collect", script_functions::range_to_array); let _ = engine.register_custom_operator("gt", 200); let _ = engine.register_custom_operator("gte", 200); let _ = engine.register_custom_operator("<<", 200); @@ -217,9 +224,22 @@ impl Add for Series { } impl Series { - pub fn s_head(&mut self, n: i64) -> Series { + pub fn s_head(&mut self) -> Series { + Series(self.0.head(Some(1))) + } + + pub fn s_head_len(&mut self, n: i64) -> Series { Series(self.0.head(Some(n as usize))) } + + pub fn s_tail(&mut self) -> Series { + Series(self.0.tail(Some(self.0.len() - 1))) + } + + pub fn s_tail_len(&mut self, size: i64) -> Series { + Series(self.0.tail(Some(size as usize))) + } + pub fn s_sort(&mut self, reverse: bool) -> Series { Series(self.0.sort(reverse)) } @@ -228,6 +248,18 @@ impl Series { self.0.sum().unwrap_or_default() } + pub fn s_mean(&mut self) -> f64 { + self.0.mean().unwrap_or_default() + } + + pub fn s_min(&mut self) -> f64 { + self.0.min().unwrap_or_default() + } + + pub fn s_max(&mut self) -> f64 { + self.0.max().unwrap_or_default() + } + pub fn s_get(&mut self, n: i64) -> ScriptResult { let value = self.get(n as usize); match value { @@ -436,6 +468,10 @@ mod script_functions { pub fn gte_op(a: &str, b: rhai::Dynamic) -> DataFrameExpression { DataFrameExpression(polars::prelude::col(a).gt_eq(DataFrameExpression::from(b))) } + + pub fn range_to_array(range: std::ops::Range) -> rhai::Array { + range.map(Dynamic::from).collect() + } } fn implementation_df_select( @@ -690,4 +726,40 @@ from data ["age"] : ["age" gt 18]; .collect::>(); assert_eq!(s, vec![Some(22), Some(32)]); } + + #[test] + pub fn test_range_to_array() { + let res = process(r#"series((1..4).collect())"#); + let s = res + .into_series() + .i64() + .unwrap() + .into_iter() + .collect::>(); + assert_eq!(s, vec![Some(1), Some(2), Some(3)]); + } + + #[test] + pub fn test_series_min() { + let res = process(r#"series((2..4).collect()).min()"#); + dbg!(&res); + let s = res.into_scalar(); + assert_eq!(s, "2.0"); + } + + #[test] + pub fn test_series_max() { + let res = process(r#"series((2..4).collect()).max()"#); + dbg!(&res); + let s = res.into_scalar(); + assert_eq!(s, "3.0"); + } + + #[test] + pub fn test_series_mean() { + let res = process(r#"series((1..51).collect()).mean()"#); + dbg!(&res); + let s = res.into_scalar(); + assert_eq!(s, "25.5"); + } } diff --git a/abacus-core/src/engine.rs b/abacus-core/src/engine.rs index 3e89a38..f864b9b 100644 --- a/abacus-core/src/engine.rs +++ b/abacus-core/src/engine.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use crate::dataframe; #[derive(Debug)] diff --git a/abacus-ui/README.org b/abacus-ui/README.org index dea358a..67f7569 100644 --- a/abacus-ui/README.org +++ b/abacus-ui/README.org @@ -32,6 +32,13 @@ In normal mode the cursor is a block and functional keybinds can be used for mov | w | Scan forward a word | | v | Mark selection | | x | Delete current character or selection | +| dd | Delete the current line | +| cw | Change word forward | +| cb | Change word backward | +| y | Yank selection | +| Y | Yank line | +| p | Paste line at cursor | +| P | Paste line below | ** Insert mode In insert mode the cursor is a line and text can be edited. To return to normal mode use the ESC key. diff --git a/abacus-ui/src/data/app_data.rs b/abacus-ui/src/data/app_data.rs index d2d0b5a..097b65d 100644 --- a/abacus-ui/src/data/app_data.rs +++ b/abacus-ui/src/data/app_data.rs @@ -46,26 +46,6 @@ impl Default for AppData { } } -impl AppData { - pub fn process(&mut self) { - let mut engine = abacus_core::Engine::default(); - - for mut block in self.blocks.iter_mut() { - let start = std::time::Instant::now(); - block.output = engine.process_script(&block.editor_data.content.to_string()); - println!("block executed in {}", start.elapsed().as_millis()); - } - } - pub fn process_block(&mut self, index: usize) { - let mut engine = abacus_core::Engine::default(); - if let Some(block) = self.blocks.get_mut(index) { - let start = std::time::Instant::now(); - block.output = engine.process_script(&block.editor_data.content.to_string()); - println!("block executed in {}", start.elapsed().as_millis()); - } - } -} - #[derive(Clone, Data, Lens, Default, PartialEq, Debug)] pub struct Block { pub name: String, diff --git a/abacus-ui/src/data/editor_data.rs b/abacus-ui/src/data/editor_data.rs index 09a1e53..24c590a 100644 --- a/abacus-ui/src/data/editor_data.rs +++ b/abacus-ui/src/data/editor_data.rs @@ -104,37 +104,63 @@ impl EditorData { self.deselect(); } - pub fn word_scan_forward(&mut self) { - while self.cursor_pos < self.content.len_chars() { - self.cursor_pos += 1; - if !self - .current_char() - .map(|c| c.is_alphanumeric()) - .unwrap_or(false) + pub fn get_word_scan_forward_position(&mut self, skip_first: bool) -> usize { + if self.cursor_pos < self.content.len_chars() { + for (idx, ch) in self + .content + .chars_at(self.cursor_pos) + .skip(if skip_first { 1 } else { 0 }) + .enumerate() { - break; + if !ch.is_alphanumeric() { + return (idx + self.cursor_pos + 2).min(self.content.len_chars()); + } } - self.deselect(); + self.content.len_chars() + } else { + self.content.len_chars() } } - pub fn word_scan_backward(&mut self) { - if self.cursor_pos > 0 { - self.cursor_pos -= 1; + pub fn get_word_scan_backward_position(&mut self, skip_first: bool) -> usize { + let mut pos = self.cursor_pos; + if pos > 0 && skip_first { + pos -= 1; } - while self.cursor_pos > 0 { + while pos > 0 { if !self .content - .get_char(self.cursor_pos - 1) + .get_char(pos - 1) .map(|c| c.is_alphanumeric()) .unwrap_or(false) { break; } else { - self.cursor_pos -= 1; + pos -= 1; } - self.deselect(); } + pos + } + + pub fn delete_word_forward(&mut self) { + let end = self.get_word_scan_forward_position(false); + self.content.remove(self.cursor_pos..end); + } + + pub fn delete_word_backward(&mut self) { + let start = self.get_word_scan_backward_position(true); + self.content.remove(start..self.cursor_pos); + self.cursor_pos = start; + } + + pub fn word_scan_forward(&mut self) { + self.cursor_pos = self.get_word_scan_forward_position(true); + self.deselect(); + } + + pub fn word_scan_backward(&mut self) { + self.cursor_pos = self.get_word_scan_backward_position(true); + self.deselect(); } pub fn delete_char_forward(&mut self) { @@ -201,15 +227,8 @@ impl EditorData { } pub fn select_up(&mut self) { - let line_idx = self.content.char_to_line(self.cursor_pos); - - if line_idx > 0 { - let start_of_current_line = self.content.line_to_char(line_idx); - let line_pos = self.cursor_pos - start_of_current_line; - let up_line_start = self.content.line_to_char(line_idx - 1); - let up_line = self.content.line(line_idx - 1); - self.cursor_pos = up_line_start + line_pos.min(up_line.len_chars() - 1); - } + self.start_selection(); + self.cursor_up(); } pub fn cursor_down(&mut self) { @@ -225,17 +244,8 @@ impl EditorData { } pub fn select_down(&mut self) { - //12 -> 11 - let line_idx = self.content.char_to_line(self.cursor_pos); - if line_idx < self.content.len_lines() - 1 { - let start_of_current_line = self.content.line_to_char(line_idx); - let line_pos = self.cursor_pos - start_of_current_line; - let start_of_next_line = self.content.line_to_char(line_idx + 1) + 1; - let next_line_len = self.content.line(line_idx + 1).len_chars(); - - self.cursor_pos = - (start_of_next_line + line_pos.min(next_line_len)).min(start_of_next_line); - } + self.start_selection(); + self.cursor_down(); } pub fn cursor_right(&mut self) { @@ -281,21 +291,24 @@ impl EditorData { self.deselect(); } + pub fn insert_mode(&mut self) { + self.mode = EditMode::Insert; + self.deselect(); + } + pub fn select_all(&mut self) { self.selection_pos = Some(0); self.cursor_pos = self.content.len_chars(); } pub fn select_left(&mut self) { - if self.cursor_pos > 0 { - self.cursor_pos -= 1 - } + self.start_selection(); + self.cursor_left(); } pub fn select_right(&mut self) { - if self.cursor_pos < self.content.len_chars() { - self.cursor_pos += 1; - } + self.start_selection(); + self.cursor_right(); } pub fn current_line(&self) -> ropey::RopeSlice { @@ -310,6 +323,12 @@ impl EditorData { .unwrap() } + pub fn start_selection(&mut self) { + if self.selection_pos.is_none() { + self.selection_pos = Some(self.cursor_pos); + } + } + pub fn current_line_index(&self) -> usize { self.content.char_to_line(self.cursor_pos) } diff --git a/abacus-ui/src/editor.rs b/abacus-ui/src/editor.rs index 5be335d..d462705 100644 --- a/abacus-ui/src/editor.rs +++ b/abacus-ui/src/editor.rs @@ -36,6 +36,12 @@ mod keymap { pub const WORD_FORWARD: &str = "w"; pub const WORD_BACK: &str = "b"; pub const SELECT_MODE: &str = "v"; + pub const CHANGE_WORD_FORWARD: &str = "cw"; + pub const CHANGE_WORD_BACKWARD: &str = "cb"; + pub const YANK: &str = "y"; + pub const YANK_LINE: &str = "Y"; + pub const PASTE: &str = "p"; + pub const PASTE_LINE: &str = "P"; #[derive(Debug, Default)] pub struct KeyList { @@ -63,6 +69,12 @@ mod keymap { WORD_FORWARD, WORD_BACK, SELECT_MODE, + CHANGE_WORD_FORWARD, + CHANGE_WORD_BACKWARD, + YANK, + YANK_LINE, + PASTE, + PASTE_LINE, ], } } @@ -149,7 +161,7 @@ impl AbacusEditor { } if data.mode == EditMode::Normal { - let char_rect = if data.content.len_chars() == 0 { + let char_rect = if data.cursor_pos == 0 { let rects = layout.rects_for_range(0..1); rects.first().cloned() } else if data.current_char() == Some('\n') @@ -358,7 +370,59 @@ impl Widget for AbacusEditor { data.word_scan_backward(); } Some(keymap::SELECT_MODE) => { - data.selection_pos = Some(data.cursor_pos); + data.cursor_to_end_of_line(); + data.start_selection(); + data.cursor_to_start_of_line(); + } + Some(keymap::CHANGE_WORD_FORWARD) => { + data.delete_word_forward(); + data.insert_mode(); + } + Some(keymap::CHANGE_WORD_BACKWARD) => { + data.delete_word_backward(); + data.insert_mode(); + } + Some(keymap::YANK) => { + if data.selection_pos.is_some() { + let mut cb: clipboard::ClipboardContext = + clipboard::ClipboardProvider::new().unwrap(); + if let Some(slice) = data.content.get_slice(data.select_range()) + { + let _ = cb.set_contents(slice.to_string()); + } + } + data.deselect(); + } + Some(keymap::YANK_LINE) => { + data.cursor_to_start_of_line(); + data.start_selection(); + data.cursor_to_end_of_line(); + if data.selection_pos.is_some() { + let mut cb: clipboard::ClipboardContext = + clipboard::ClipboardProvider::new().unwrap(); + if let Some(slice) = data.content.get_slice(data.select_range()) + { + let _ = cb.set_contents(slice.to_string()); + } + } + data.deselect(); + } + Some(keymap::PASTE) => { + let mut cb: clipboard::ClipboardContext = + clipboard::ClipboardProvider::new().unwrap(); + data.push_str(&cb.get_contents().unwrap_or_default()); + ctx.request_layout(); + } + Some(keymap::PASTE_LINE) => { + if data.selection_pos.is_some() { + data.delete_char_back(); + } + let mut cb: clipboard::ClipboardContext = + clipboard::ClipboardProvider::new().unwrap(); + data.cursor_to_end_of_line(); + data.push_str("\n"); + data.push_str(&cb.get_contents().unwrap_or_default()); + ctx.request_layout(); } _ => {} } @@ -414,7 +478,6 @@ impl Widget for AbacusEditor { druid::keyboard_types::Key::ArrowUp if e.mods.shift() => { data.select_up(); - data.deselect(); } druid::keyboard_types::Key::ArrowDown if e.mods.shift() => { data.select_down(); @@ -482,6 +545,9 @@ impl Widget for AbacusEditor { ctx.request_paint(); } } + Event::WindowConnected => { + ctx.request_focus(); + } _ => {} } }