From 2aa3aa47c02d93d998563c240559e0799e9b7c0e Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Wed, 30 Mar 2022 21:56:05 -0400 Subject: [PATCH] implement ACTUAL autocompletion --- build.rs | 28 +-------- src/egui_app.rs | 1 - src/function.rs | 91 ++++++++++++++++++++++++----- src/suggestions.rs | 139 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 185 insertions(+), 74 deletions(-) diff --git a/build.rs b/build.rs index dce1d4e..34d1cfe 100644 --- a/build.rs +++ b/build.rs @@ -15,7 +15,7 @@ fn main() { .run(); shadow_rs::new().unwrap(); - generate_hashmap(); + // generate_hashmap(); } fn generate_hashmap() { @@ -37,7 +37,7 @@ const SUPPORTED_FUNCTIONS: [&str; 22] = [ "round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10", ]; -const CONST: char = '"'; +const QUOTE: char = '"'; fn compile_hashmap() -> phf_codegen::Map { let functions_processed: Vec = SUPPORTED_FUNCTIONS @@ -92,7 +92,7 @@ fn compile_hashmap() -> phf_codegen::Map { if keys.iter().filter(|a| a == &&key).count() == 1 { output.entry( key.clone(), - &format!("HintEnum::Single({}{}{})", CONST, value, CONST), + &format!("HintEnum::Single({}{}{})", QUOTE, value, QUOTE), ); } else { let multi_data = tuple_list_1 @@ -106,25 +106,3 @@ fn compile_hashmap() -> phf_codegen::Map { output } - -fn common_substring<'a>(a: &'a str, b: &'a str) -> Option { - let a_chars: Vec = a.chars().collect(); - let b_chars: Vec = b.chars().collect(); - if a_chars[0] != b_chars[0] { - return None; - } - - let mut last_value: String = a_chars[0].to_string(); - let max_common_i = std::cmp::min(a.len(), b.len()) - 1; - for i in 1..=max_common_i { - let a_i = a_chars[i]; - let b_i = b_chars[i]; - if a_i == b_i { - last_value += &a_i.to_string() - } else { - break; - } - } - - Some(last_value) -} diff --git a/src/egui_app.rs b/src/egui_app.rs index a0d935e..094c2df 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -2,7 +2,6 @@ use crate::consts::*; use crate::function::{FunctionEntry, Riemann, DEFAULT_FUNCTION_ENTRY}; use crate::misc::{dyn_mut_iter, option_vec_printer, JsonFileOutput, SerdeValueHelper}; use crate::parsing::{process_func_str, test_func}; -use crate::suggestions::auto_complete; use eframe::{egui, epi}; use egui::plot::Plot; use egui::{ diff --git a/src/function.rs b/src/function.rs index 7bd56a0..9800a68 100644 --- a/src/function.rs +++ b/src/function.rs @@ -3,8 +3,8 @@ use crate::egui_app::AppSettings; use crate::misc::*; use crate::parsing::BackingFunction; -use crate::suggestions::generate_hint; -use eframe::{egui, epaint, epaint}; +use crate::suggestions::{generate_hint, HintEnum}; +use eframe::{egui, epaint}; use egui::{ epaint::text::cursor::Cursor, text::CCursor, text_edit::CursorRange, Key, TextEdit, Widget, }; @@ -65,7 +65,7 @@ pub struct FunctionEntry { extrema_data: Option>, roots_data: Option>, - auto_complete_i: Option, + auto_complete_i: usize, } impl Default for FunctionEntry { @@ -83,7 +83,7 @@ impl Default for FunctionEntry { derivative_data: None, extrema_data: None, roots_data: None, - auto_complete_i: None, + auto_complete_i: 0, } } } @@ -116,22 +116,83 @@ impl FunctionEntry { } } + /// Returns whether or not the hint was applied pub fn auto_complete(&mut self, ui: &mut egui::Ui, string: &mut String) -> bool { let te_id = ui.make_persistent_id("text_edit_ac".to_string()); - let hint = generate_hint(&string).unwrap_or_default(); + let hint = generate_hint(string.clone()); - let func_edit_focus = egui::TextEdit::singleline(string) - .hint_text(&hint) - .hint_forward(true) - .id(te_id) - .ui(ui) - .has_focus(); + let mut func_edit = egui::TextEdit::singleline(string).hint_forward(true); + + let hint_text = hint.get_single().unwrap_or_default(); + if !hint_text.is_empty() { + let func_edit_2 = func_edit; + func_edit = func_edit_2.hint_text(&hint_text); + } + + let re = func_edit.id(te_id).ui(ui); + + let func_edit_focus = re.has_focus(); // If in focus and right arrow key was pressed, apply hint if func_edit_focus { - if ui.input().key_down(Key::ArrowRight) { - *string = string.clone() + &hint; + let mut push_cursor: bool = false; + let right_arrow = ui.input().key_pressed(Key::ArrowRight); + + if !hint_text.is_empty() && right_arrow { + push_cursor = true; + *string = string.clone() + &hint_text; + } else if hint.is_multi() { + let selections = match hint { + HintEnum::Many(selections) => selections, + _ => unimplemented!(), + }; + + let max_i = selections.len() as i16 - 1; + + let mut i = self.auto_complete_i as i16; + + if ui.input().key_pressed(Key::ArrowDown) { + if i + 1 > max_i { + i = 0; + } else { + i += 1; + } + } else if ui.input().key_pressed(Key::ArrowUp) { + if 0 > i - 1 { + i = max_i; + } else { + i -= 1; + } + } + + self.auto_complete_i = i as usize; + + let popup_id = ui.make_persistent_id("autocomplete_popup"); + + let mut clicked = false; + egui::popup_below_widget(ui, popup_id, &re, |ui| { + re.request_focus(); + for (i, candidate) in selections.iter().enumerate() { + if ui + .selectable_label(i == self.auto_complete_i, *candidate) + .clicked() + { + clicked = true; + self.auto_complete_i = i; + } + } + }); + + if right_arrow | clicked { + *string = string.clone() + &selections[self.auto_complete_i]; + push_cursor = true; + } else { + ui.memory().open_popup(popup_id); + } + } + + if push_cursor { let mut state = TextEdit::load_state(ui.ctx(), te_id).unwrap(); state.set_cursor_range(Some(CursorRange::one(Cursor { ccursor: CCursor { @@ -147,9 +208,9 @@ impl FunctionEntry { }))); TextEdit::store_state(ui.ctx(), te_id, state); } - return true; } - return false; + + return func_edit_focus; } /// Creates and does the math for creating all the rectangles under the diff --git a/src/suggestions.rs b/src/suggestions.rs index ede8994..fb76767 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -1,9 +1,9 @@ use crate::misc::chars_take; /// Generate a hint based on the input `input`, returns an `Option` -pub fn generate_hint(input: &str) -> Option { +pub fn generate_hint(input: String) -> HintEnum<'static> { if input.is_empty() { - return Some(HintEnum::Single("x^2")); + return HintEnum::Single("x^2"); } let chars: Vec = input.chars().collect(); @@ -17,46 +17,25 @@ pub fn generate_hint(input: &str) -> Option { }); if open_parens > closed_parens { - return Some(HintEnum::Single(")")); + return HintEnum::Single(")"); } let len = chars.len(); - if len >= 5 { - let result_five = get_completion(chars_take(&chars, 5)); - if result_five.is_some() { - return result_five; + for i in (2..=5).rev().filter(|i| len >= *i) { + if let Some(output) = get_completion(chars_take(&chars, i)) { + return output; } } - if len >= 4 { - let result_four = get_completion(chars_take(&chars, 4)); - if result_four.is_some() { - return result_four; - } - } - - if len >= 3 { - let result_three = get_completion(chars_take(&chars, 3)); - if result_three.is_some() { - return result_three; - } - } - - if len >= 2 { - let result_two = get_completion(chars_take(&chars, 2)); - if result_two.is_some() { - return result_two; - } - } - - None + HintEnum::None } #[derive(Clone, PartialEq)] pub enum HintEnum<'a> { Single(&'static str), Many(&'a [&'static str]), + None, } impl std::fmt::Debug for HintEnum<'static> { @@ -74,20 +53,114 @@ impl ToString for HintEnum<'static> { .map(|a| a.to_string()) .collect::() .to_string(), + HintEnum::None => String::new(), } } } impl HintEnum<'static> { - pub fn ensure_single(&self) -> String { + pub fn get_single(&self) -> Option { match self { - HintEnum::Single(single_data) => single_data.to_string(), - HintEnum::Many(_) => String::new(), + HintEnum::Single(x) => Some(x.to_string()), + _ => None, + } + } + + pub fn is_multi(&self) -> bool { + match self { + HintEnum::Many(_) => true, + _ => false, } } } -include!(concat!(env!("OUT_DIR"), "/codegen.rs")); +// include!(concat!(env!("OUT_DIR"), "/codegen.rs")); +static COMPLETION_HASHMAP: phf::Map<&'static str, HintEnum> = ::phf::Map { + key: 2980949210194914378, + disps: &[ + (0, 5), + (0, 24), + (1, 0), + (3, 14), + (51, 0), + (0, 11), + (2, 0), + (0, 29), + (3, 23), + (23, 59), + (0, 5), + (0, 7), + (39, 43), + ], + entries: &[ + ("co", HintEnum::Many(&["s(", "sh("])), + ("c", HintEnum::Many(&["os(", "osh(", "eil(", "brt("])), + ("frac", HintEnum::Single("t(")), + ("fl", HintEnum::Single("oor(")), + ("sq", HintEnum::Single("rt(")), + ("fr", HintEnum::Single("act(")), + ("sig", HintEnum::Single("num(")), + ("ac", HintEnum::Single("os(")), + ("signum", HintEnum::Single("(")), + ("ln", HintEnum::Single("(")), + ("aco", HintEnum::Single("s(")), + ("fra", HintEnum::Single("ct(")), + ("round", HintEnum::Single("(")), + ("t", HintEnum::Many(&["an(", "anh(", "runc("])), + ("s", HintEnum::Many(&["ignum(", "in(", "inh(", "qrt("])), + ("acos", HintEnum::Single("(")), + ("exp", HintEnum::Single("(")), + ("tanh", HintEnum::Single("(")), + ("lo", HintEnum::Many(&["g2(", "g10("])), + ("log10", HintEnum::Single("(")), + ("fract", HintEnum::Single("(")), + ("trun", HintEnum::Single("c(")), + ("log1", HintEnum::Single("0(")), + ("at", HintEnum::Single("an(")), + ("tr", HintEnum::Single("unc(")), + ("floor", HintEnum::Single("(")), + ("ab", HintEnum::Single("s(")), + ("si", HintEnum::Many(&["gnum(", "n(", "nh("])), + ("asi", HintEnum::Single("n(")), + ("sin", HintEnum::Many(&["(", "h("])), + ("e", HintEnum::Single("xp(")), + ("flo", HintEnum::Single("or(")), + ("ex", HintEnum::Single("p(")), + ("sqr", HintEnum::Single("t(")), + ("log2", HintEnum::Single("(")), + ("atan", HintEnum::Single("(")), + ("sinh", HintEnum::Single("(")), + ("tru", HintEnum::Single("nc(")), + ("cei", HintEnum::Single("l(")), + ("l", HintEnum::Many(&["n(", "og2(", "og10("])), + ("asin", HintEnum::Single("(")), + ("tan", HintEnum::Many(&["(", "h("])), + ("cos", HintEnum::Many(&["(", "h("])), + ("roun", HintEnum::Single("d(")), + ("as", HintEnum::Single("in(")), + ("r", HintEnum::Single("ound(")), + ("log", HintEnum::Many(&["2(", "10("])), + ("ta", HintEnum::Many(&["n(", "nh("])), + ("floo", HintEnum::Single("r(")), + ("cbrt", HintEnum::Single("(")), + ("ata", HintEnum::Single("n(")), + ("ce", HintEnum::Single("il(")), + ("abs", HintEnum::Single("(")), + ("cosh", HintEnum::Single("(")), + ("cbr", HintEnum::Single("t(")), + ("rou", HintEnum::Single("nd(")), + ("signu", HintEnum::Single("m(")), + ("a", HintEnum::Many(&["bs(", "sin(", "cos(", "tan("])), + ("sqrt", HintEnum::Single("(")), + ("ceil", HintEnum::Single("(")), + ("ro", HintEnum::Single("und(")), + ("f", HintEnum::Many(&["loor(", "ract("])), + ("sign", HintEnum::Single("um(")), + ("trunc", HintEnum::Single("(")), + ("cb", HintEnum::Single("rt(")), + ], +}; + /// Gets completion from `COMPLETION_HASHMAP` pub fn get_completion(key: String) -> Option> { if key.is_empty() {