split up AutoComplete logic for testability
This commit is contained in:
parent
95fb39715f
commit
66c56a015a
@ -41,16 +41,16 @@ pub fn generate_hint<'a>(input: &str) -> &'a HintEnum<'a> {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum HintEnum<'a> {
|
pub enum HintEnum<'a> {
|
||||||
Single(&'static str),
|
Single(&'a str),
|
||||||
Many(&'a [&'static str]),
|
Many(&'a [&'a str]),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for HintEnum<'static> {
|
impl<'a> std::fmt::Debug for HintEnum<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) }
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for HintEnum<'static> {
|
impl<'a> std::fmt::Display for HintEnum<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
HintEnum::Single(single_data) => {
|
HintEnum::Single(single_data) => {
|
||||||
@ -66,7 +66,7 @@ impl std::fmt::Display for HintEnum<'static> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HintEnum<'static> {
|
impl<'a> HintEnum<'a> {
|
||||||
pub fn is_some(&self) -> bool { !matches!(self, HintEnum::None) }
|
pub fn is_some(&self) -> bool { !matches!(self, HintEnum::None) }
|
||||||
|
|
||||||
pub fn is_none(&self) -> bool { !self.is_some() }
|
pub fn is_none(&self) -> bool { !self.is_some() }
|
||||||
|
|||||||
212
src/widgets.rs
212
src/widgets.rs
@ -1,18 +1,26 @@
|
|||||||
use crate::suggestions::{generate_hint, HintEnum};
|
use crate::suggestions::{generate_hint, HintEnum};
|
||||||
use eframe::{egui, epaint};
|
use eframe::{egui, epaint};
|
||||||
use egui::{text::CCursor, text_edit::CursorRange, Key, Modifiers, TextEdit, Widget};
|
use egui::{text::CCursor, text_edit::CursorRange, Key, Label, Modifiers, TextEdit, Widget};
|
||||||
use epaint::text::cursor::{Cursor, PCursor, RCursor};
|
use epaint::text::cursor::{Cursor, PCursor, RCursor};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AutoComplete<'a> {
|
pub struct AutoComplete<'a> {
|
||||||
pub i: usize,
|
pub i: usize,
|
||||||
pub hint: &'a HintEnum<'static>,
|
pub hint: &'a HintEnum<'a>,
|
||||||
pub func_str: Option<String>,
|
pub func_str: Option<String>,
|
||||||
pub changed: bool,
|
pub changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AutoComplete<'static> {
|
#[derive(PartialEq, Debug)]
|
||||||
fn default() -> AutoComplete<'static> {
|
enum Movement {
|
||||||
|
Complete,
|
||||||
|
Down,
|
||||||
|
Up,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for AutoComplete<'a> {
|
||||||
|
fn default() -> AutoComplete<'a> {
|
||||||
AutoComplete {
|
AutoComplete {
|
||||||
i: 0,
|
i: 0,
|
||||||
hint: &HintEnum::None,
|
hint: &HintEnum::None,
|
||||||
@ -24,18 +32,61 @@ impl Default for AutoComplete<'static> {
|
|||||||
|
|
||||||
impl<'a> AutoComplete<'a> {
|
impl<'a> AutoComplete<'a> {
|
||||||
fn changed(&mut self, string: &str) {
|
fn changed(&mut self, string: &str) {
|
||||||
if self.func_str != Some(string.to_string()) {
|
let new_func_str = Some(string.to_string());
|
||||||
|
if self.func_str != new_func_str {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
self.func_str = Some(string.to_string());
|
self.func_str = new_func_str;
|
||||||
self.hint = generate_hint(string);
|
self.hint = generate_hint(string);
|
||||||
} else {
|
} else {
|
||||||
self.changed = false;
|
self.changed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interact_back(&mut self, new_string: &mut String, movement: &Movement) {
|
||||||
|
match self.hint {
|
||||||
|
HintEnum::Many(hints) => {
|
||||||
|
if movement == &Movement::Complete {
|
||||||
|
*new_string += hints[self.i];
|
||||||
|
return;
|
||||||
|
} else if movement == &Movement::None {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_i = hints.len() as i16 - 1;
|
||||||
|
let mut i = self.i as i16;
|
||||||
|
|
||||||
|
match movement {
|
||||||
|
Movement::Up => {
|
||||||
|
i -= 1;
|
||||||
|
if 0 > i {
|
||||||
|
i = max_i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Movement::Down => {
|
||||||
|
i += 1;
|
||||||
|
if i > max_i {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.i = i as usize;
|
||||||
|
}
|
||||||
|
HintEnum::Single(hint) => match movement {
|
||||||
|
Movement::Complete => {
|
||||||
|
*new_string += hint;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
HintEnum::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, string: String, func_i: i32) -> (String, bool) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, string: String, func_i: i32) -> (String, bool) {
|
||||||
let mut new_string = string.clone();
|
let mut new_string = string.clone();
|
||||||
|
|
||||||
|
let mut movement: Movement = Movement::None;
|
||||||
|
|
||||||
// update self
|
// update self
|
||||||
self.changed(&string);
|
self.changed(&string);
|
||||||
|
|
||||||
@ -54,6 +105,9 @@ impl<'a> AutoComplete<'a> {
|
|||||||
// Put here so these key presses don't interact with other elements
|
// Put here so these key presses don't interact with other elements
|
||||||
let enter_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Enter);
|
let enter_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Enter);
|
||||||
let tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab);
|
let tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab);
|
||||||
|
if enter_pressed | tab_pressed | ui.input().key_pressed(Key::ArrowRight) {
|
||||||
|
movement = Movement::Complete;
|
||||||
|
}
|
||||||
|
|
||||||
if let HintEnum::Single(single_hint) = self.hint {
|
if let HintEnum::Single(single_hint) = self.hint {
|
||||||
let func_edit_2 = func_edit;
|
let func_edit_2 = func_edit;
|
||||||
@ -66,68 +120,56 @@ impl<'a> AutoComplete<'a> {
|
|||||||
|
|
||||||
// If in focus and right arrow key was pressed, apply hint
|
// If in focus and right arrow key was pressed, apply hint
|
||||||
if func_edit_focus {
|
if func_edit_focus {
|
||||||
let apply_key = ui.input().key_pressed(Key::ArrowRight) | enter_pressed | tab_pressed;
|
if ui.input().key_pressed(Key::ArrowDown) {
|
||||||
|
movement = Movement::Down;
|
||||||
|
} else if ui.input().key_pressed(Key::ArrowUp) {
|
||||||
|
movement = Movement::Up;
|
||||||
|
}
|
||||||
|
|
||||||
let push_cursor: bool = match self.hint {
|
// if movement != Movement::None {
|
||||||
HintEnum::Single(hint) => {
|
// println!("{:?}", movement);
|
||||||
if apply_key {
|
// }
|
||||||
new_string += hint;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HintEnum::Many(hints) => {
|
|
||||||
let max_i = hints.len() as i16 - 1;
|
|
||||||
|
|
||||||
let mut i = self.i as i16;
|
self.interact_back(&mut new_string, &movement);
|
||||||
|
|
||||||
if ui.input().key_pressed(Key::ArrowDown) {
|
// TODO: fix clicking on labels (no clue why it doesn't work, time to take a walk)
|
||||||
i += 1;
|
if movement != Movement::Complete && let HintEnum::Many(hints) = self.hint {
|
||||||
if i > max_i {
|
// Doesn't need to have a number in id as there should only be 1 autocomplete popup in the entire gui
|
||||||
i = 0;
|
let popup_id = ui.make_persistent_id("autocomplete_popup");
|
||||||
|
|
||||||
|
// let mut clicked = false;
|
||||||
|
|
||||||
|
egui::popup_below_widget(ui, popup_id, &re, |ui| {
|
||||||
|
hints.iter().enumerate().for_each(|(i, candidate)| {
|
||||||
|
/*
|
||||||
|
if ui.selectable_label(i == self.i, *candidate).clicked() {
|
||||||
|
clicked = true;
|
||||||
|
self.i = i;
|
||||||
}
|
}
|
||||||
} else if ui.input().key_pressed(Key::ArrowUp) {
|
*/
|
||||||
i -= 1;
|
|
||||||
if 0 > i {
|
|
||||||
i = max_i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.i = i as usize;
|
// placeholder for now
|
||||||
|
ui.add_enabled(i == self.i, Label::new(*candidate));
|
||||||
// Doesn't need to have a number in id as there should only be 1 autocomplete
|
|
||||||
// popup in entire gui
|
|
||||||
let popup_id = ui.make_persistent_id("autocomplete_popup");
|
|
||||||
|
|
||||||
let mut clicked = false;
|
|
||||||
|
|
||||||
egui::popup_below_widget(ui, popup_id, &re, |ui| {
|
|
||||||
for (i, candidate) in hints.iter().enumerate() {
|
|
||||||
if ui.selectable_label(i == self.i, *candidate).clicked() {
|
|
||||||
clicked = true;
|
|
||||||
self.i = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if clicked | apply_key {
|
ui.memory().open_popup(popup_id)
|
||||||
new_string += hints[self.i];
|
/*
|
||||||
|
if clicked {
|
||||||
|
new_string += hints[self.i];
|
||||||
|
|
||||||
// don't need this here as it simply won't be display next frame in `math_app.rs`
|
// don't need this here as it simply won't be display next frame in `math_app.rs`
|
||||||
// ui.memory().close_popup();
|
// ui.memory().close_popup();
|
||||||
|
|
||||||
true
|
movement = Movement::Complete;
|
||||||
} else {
|
} else {
|
||||||
ui.memory().open_popup(popup_id);
|
ui.memory().open_popup(popup_id);
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => false,
|
*/
|
||||||
};
|
}
|
||||||
|
|
||||||
// Push cursor to end if needed
|
// Push cursor to end if needed
|
||||||
if push_cursor {
|
if movement == Movement::Complete {
|
||||||
let mut state = TextEdit::load_state(ui.ctx(), te_id).unwrap();
|
let mut state = TextEdit::load_state(ui.ctx(), te_id).unwrap();
|
||||||
state.set_cursor_range(Some(CursorRange::one(Cursor {
|
state.set_cursor_range(Some(CursorRange::one(Cursor {
|
||||||
ccursor: CCursor {
|
ccursor: CCursor {
|
||||||
@ -147,3 +189,61 @@ impl<'a> AutoComplete<'a> {
|
|||||||
(new_string, func_edit_focus)
|
(new_string, func_edit_focus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn auto_complete_helper(string: &str, movement: Movement) -> (AutoComplete, String) {
|
||||||
|
let mut auto_complete = AutoComplete::default();
|
||||||
|
auto_complete.changed(string);
|
||||||
|
let mut string_1 = String::from(string);
|
||||||
|
auto_complete.interact_back(&mut string_1, &movement);
|
||||||
|
|
||||||
|
(auto_complete, string_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_complete_single_still() {
|
||||||
|
let (_, string) = auto_complete_helper("", Movement::None);
|
||||||
|
|
||||||
|
assert_eq!(&*string, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_complete_single_complete() {
|
||||||
|
let (_, string) = auto_complete_helper("", Movement::Complete);
|
||||||
|
|
||||||
|
assert_eq!(&*string, "x^2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_complete_single_down() {
|
||||||
|
let (_, string) = auto_complete_helper("", Movement::Down);
|
||||||
|
|
||||||
|
assert_eq!(&*string, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_complete_single_up() {
|
||||||
|
let (_, string) = auto_complete_helper("", Movement::Up);
|
||||||
|
|
||||||
|
assert_eq!(&*string, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_complete_multi_down() {
|
||||||
|
let (auto_complete, string) = auto_complete_helper("s", Movement::Down);
|
||||||
|
|
||||||
|
assert_eq!(auto_complete.i, 1);
|
||||||
|
assert_eq!(&*string, "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_complete_multi_complete() {
|
||||||
|
let (auto_complete, string) = auto_complete_helper("s", Movement::Complete);
|
||||||
|
|
||||||
|
assert_eq!(auto_complete.i, 0);
|
||||||
|
assert_eq!(&*string, "sin(");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user