From b0998fad4d28524e4ec9198df0b7580f5eea71f7 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 2 Jun 2024 18:14:40 -0400 Subject: [PATCH] feat: show menu name in bar. - Disable cursor highlighting in Aux&Metadata docs, for realsies. - Construct "String" docs that consist of just a single string. - Add a menu name doc and show it in the pane notation. - Use later version of ppp (needed for PaneNotation::style). --- Cargo.toml | 2 +- data/string_lang.ron | 26 ++++++++++ scripts/init.rhai | 4 +- src/engine/doc.rs | 18 +++++-- src/engine/doc_set.rs | 10 ++-- src/engine/engine.rs | 15 +++++- src/keymap/layer.rs | 11 +++-- src/keymap/menu.rs | 8 ++- src/pretty_doc.rs | 28 ++++++++--- src/runtime.rs | 102 ++++++++++++++++++++++++++++++--------- src/util/fuzzy_search.rs | 2 +- tests/tests.rs | 6 +-- 12 files changed, 182 insertions(+), 50 deletions(-) create mode 100644 data/string_lang.ron diff --git a/Cargo.toml b/Cargo.toml index c893146..7323333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ ron = "0.8.1" [dependencies.partial-pretty-printer] #path = "../ppp/" git = "https://github.com/justinpombrio/partial-pretty-printer" - version = "0.8.0" + version = "0.9.0" features = ["serialization"] [dependencies.no-nonsense-flamegraphs] version = "0.2.*" diff --git a/data/string_lang.ron b/data/string_lang.ron new file mode 100644 index 0000000..e4868de --- /dev/null +++ b/data/string_lang.ron @@ -0,0 +1,26 @@ +LanguageSpec( + name: "string", + file_extensions: [], + grammar: GrammarSpec( + constructs: [ + ConstructSpec( + name: "Root", + arity: Fixed([SortSpec(["String"])]), + ), + ConstructSpec( + name: "String", + arity: Texty, + ), + ], + sorts: [], + root_construct: "Root", + ), + default_display_notation: "DefaultDisplay", + default_source_notation: None, + notations: [ + NotationSetSpec( + name: "DefaultDisplay", + notations: [("Root", Child(0)), ("String", Text)], + ) + ] +) diff --git a/scripts/init.rhai b/scripts/init.rhai index ccdd009..c10e28c 100644 --- a/scripts/init.rhai +++ b/scripts/init.rhai @@ -33,6 +33,7 @@ fn make_char_node_selection_keymap(language_name) { } fn open_file_menu(dir) { + let dir = s::canonicalize_path(dir); let contents = s::list_files_and_dirs(dir); let keymap = new_keymap(); for file in contents.files { @@ -52,12 +53,13 @@ fn open_file_menu(dir) { open_file_menu(dir + "/.."); }); - s::open_menu("file_selection", keymap); + s::open_menu("file_selection", `Open file in ${dir}`, keymap); } s::load_language("data/keyhints_lang.ron"); s::load_language("data/selection_lang.ron"); s::load_language("data/json_lang.ron"); +s::load_language("data/string_lang.ron"); s::open_doc("data/pokemon.json"); diff --git a/src/engine/doc.rs b/src/engine/doc.rs index 0481dbf..46c3c68 100644 --- a/src/engine/doc.rs +++ b/src/engine/doc.rs @@ -73,12 +73,22 @@ impl Doc { }) } - pub fn doc_ref_source<'d>(&self, s: &'d Storage) -> DocRef<'d> { - DocRef::new_source(s, self.cursor, self.cursor.root_node(s)) + pub fn doc_ref_source<'d>(&self, s: &'d Storage, highlight_cursor: bool) -> DocRef<'d> { + let opt_cursor = if highlight_cursor { + Some(self.cursor) + } else { + None + }; + DocRef::new_source(s, opt_cursor, self.cursor.root_node(s)) } - pub fn doc_ref_display<'d>(&self, s: &'d Storage) -> DocRef<'d> { - DocRef::new_display(s, self.cursor, self.cursor.root_node(s)) + pub fn doc_ref_display<'d>(&self, s: &'d Storage, highlight_cursor: bool) -> DocRef<'d> { + let opt_cursor = if highlight_cursor { + Some(self.cursor) + } else { + None + }; + DocRef::new_display(s, opt_cursor, self.cursor.root_node(s)) } pub fn cursor(&self) -> Location { diff --git a/src/engine/doc_set.rs b/src/engine/doc_set.rs index 95f8e86..18008d6 100644 --- a/src/engine/doc_set.rs +++ b/src/engine/doc_set.rs @@ -147,7 +147,7 @@ impl DocSet { set_focus: false, }; - let (doc, opts) = match label { + let (doc, opts, highlight_cursor) = match label { DocDisplayLabel::Visible => { let doc = self.get_doc(self.visible_doc_name()?)?; let (focus_path, focus_target) = doc.cursor().path_from_root(s); @@ -158,17 +158,17 @@ impl DocSet { width_strategy: pane::WidthStrategy::NoMoreThan(settings.max_display_width), set_focus: doc.cursor().node(s).is_none(), }; - (doc, options) + (doc, options, true) } DocDisplayLabel::Metadata(name) => { let doc = self.get_doc(&DocName::Metadata(name))?; - (doc, meta_and_aux_options) + (doc, meta_and_aux_options, false) } DocDisplayLabel::Auxilliary(name) => { let doc = self.get_doc(&DocName::Auxilliary(name))?; - (doc, meta_and_aux_options) + (doc, meta_and_aux_options, false) } }; - Some((doc.doc_ref_display(s), opts)) + Some((doc.doc_ref_display(s, highlight_cursor), opts)) } } diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 57729e3..8b3eb0a 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -15,6 +15,8 @@ use std::collections::HashMap; use std::error::Error; use std::path::Path; +const STRING_LANGUAGE_NAME: &str = "string"; + #[derive(thiserror::Error, Debug)] pub enum DocError { #[error("Did not find doc named '{0}'")] @@ -243,7 +245,7 @@ impl Engine { .doc_set .get_doc(doc_name) .ok_or_else(|| DocError::DocNotFound(doc_name.to_owned()))?; - let doc_ref = doc.doc_ref_source(&self.storage); + let doc_ref = doc.doc_ref_source(&self.storage, false); let source = ppp::pretty_print_to_string(doc_ref, self.settings.max_source_width)?; Ok(source) } @@ -253,6 +255,17 @@ impl Engine { .get_content(&self.storage, label, &self.settings) } + pub fn make_string_doc(&mut self, string: String) -> Node { + let lang = self + .storage + .language(STRING_LANGUAGE_NAME) + .bug_msg("Missing String lang"); + let c_root = lang.root_construct(&self.storage); + let c_string = lang.construct(&self.storage, "String").bug(); + let string_node = Node::with_text(&mut self.storage, c_string, string).bug(); + Node::with_children(&mut self.storage, c_root, [string_node]).bug() + } + /*********** * Editing * ***********/ diff --git a/src/keymap/layer.rs b/src/keymap/layer.rs index a9d513e..5a220ec 100644 --- a/src/keymap/layer.rs +++ b/src/keymap/layer.rs @@ -203,19 +203,20 @@ impl LayerManager { &mut self, doc_name: Option<&DocName>, menu_name: String, + description: String, dynamic_keymap: Option, ) -> Result<(), SynlessError> { let composite_layer = self.composite_layer(doc_name); let label = KeymapLabel::Menu(menu_name.clone()); let menu = match (dynamic_keymap, composite_layer.keymaps.get(&label)) { (None, None) => return Err(error!(Keymap, "No keymap for menu '{menu_name}'")), - (Some(keymap), None) => Menu::new(menu_name, keymap), + (Some(keymap), None) => Menu::new(menu_name, description, keymap), (Some(dyn_keymap), Some(composite_keymap)) => { let mut keymap = composite_keymap.to_owned(); keymap.append(dyn_keymap); - Menu::new(menu_name, keymap) + Menu::new(menu_name, description, keymap) } - (None, Some(keymap)) => Menu::new(menu_name, keymap.to_owned()), + (None, Some(keymap)) => Menu::new(menu_name, description, keymap.to_owned()), }; self.active_menu = Some(menu); Ok(()) @@ -244,6 +245,10 @@ impl LayerManager { self.active_menu.is_some() } + pub fn menu_description(&self) -> Option<&str> { + self.active_menu.as_ref().map(|menu| menu.description()) + } + /********* * Input * *********/ diff --git a/src/keymap/menu.rs b/src/keymap/menu.rs index b06bee1..e2cccbf 100644 --- a/src/keymap/menu.rs +++ b/src/keymap/menu.rs @@ -19,6 +19,7 @@ pub enum MenuSelectionCmd { /// An open menu. Keeps track of the state of its candidate selection. pub struct Menu { name: MenuName, + description: String, keymap: Keymap, selection: Option, } @@ -136,14 +137,19 @@ impl MenuSelection { } impl Menu { - pub fn new(name: MenuName, keymap: Keymap) -> Menu { + pub fn new(name: MenuName, description: String, keymap: Keymap) -> Menu { Menu { name, + description, selection: MenuSelection::new(&keymap), keymap, } } + pub fn description(&self) -> &str { + &self.description + } + #[must_use] pub fn execute(&mut self, cmd: MenuSelectionCmd) -> bool { if let Some(selection) = &mut self.selection { diff --git a/src/pretty_doc.rs b/src/pretty_doc.rs index a1e54c4..e96204c 100644 --- a/src/pretty_doc.rs +++ b/src/pretty_doc.rs @@ -16,13 +16,17 @@ pub enum PrettyDocError { #[derive(Clone, Copy)] pub struct DocRef<'d> { storage: &'d Storage, - cursor_loc: Location, + cursor_loc: Option, node: Node, use_source_notation: bool, } impl<'d> DocRef<'d> { - pub fn new_display(storage: &'d Storage, cursor_loc: Location, node: Node) -> DocRef<'d> { + pub fn new_display( + storage: &'d Storage, + cursor_loc: Option, + node: Node, + ) -> DocRef<'d> { DocRef { storage, cursor_loc, @@ -31,7 +35,11 @@ impl<'d> DocRef<'d> { } } - pub fn new_source(storage: &'d Storage, cursor_loc: Location, node: Node) -> DocRef<'d> { + pub fn new_source( + storage: &'d Storage, + cursor_loc: Option, + node: Node, + ) -> DocRef<'d> { DocRef { storage, cursor_loc, @@ -91,10 +99,14 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> { Ok(match style_label { StyleLabel::Hole => HOLE_STYLE, StyleLabel::Open => { - let parent = self.cursor_loc.parent_node(self.storage); - let node_at_cursor = self.cursor_loc.node(self.storage); - if parent == Some(self.node) && node_at_cursor.is_none() { - OPEN_STYLE + if let Some(cursor_loc) = self.cursor_loc { + let parent = cursor_loc.parent_node(self.storage); + let node_at_cursor = cursor_loc.node(self.storage); + if parent == Some(self.node) && node_at_cursor.is_none() { + OPEN_STYLE + } else { + Style::default() + } } else { Style::default() } @@ -118,7 +130,7 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> { } fn node_style(self) -> Result { - let style = if self.cursor_loc.node(self.storage) == Some(self.node) { + let style = if self.cursor_loc.and_then(|loc| loc.node(self.storage)) == Some(self.node) { CURSOR_STYLE } else { Style::default() diff --git a/src/runtime.rs b/src/runtime.rs index c09545c..a545cbd 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -15,8 +15,13 @@ use std::time::Duration; // TODO: Rename Runtime -> Editor, put it in src/editor.rs? -const KEYHINTS_DOC_NAME: &str = "keyhints"; -const CANDIDATE_SELECTION_DOC_NAME: &str = "selection_menu"; +const KEYHINTS_DOC_LABEL: &str = "keyhints"; +const CANDIDATE_SELECTION_DOC_LABEL: &str = "selection_menu"; +const MENU_NAME_LABEL: &str = "menu_name"; +const MODE_LABEL: &str = "mode"; +const FILENAME_LABEL: &str = "filename"; +const SIBLING_INDEX_LABEL: &str = "sibling_index"; + const KEYHINTS_PANE_WIDTH: usize = 15; pub struct Runtime> { @@ -57,18 +62,25 @@ impl + 'static> Runtime { self.layers.remove_global_layer(layer_name) } - pub fn open_menu(&mut self, menu_name: String) -> Result<(), SynlessError> { + pub fn open_menu( + &mut self, + menu_name: String, + description: String, + ) -> Result<(), SynlessError> { let doc_name = self.engine.visible_doc_name(); - self.layers.open_menu(doc_name, menu_name, None) + self.layers + .open_menu(doc_name, menu_name, description, None) } pub fn open_menu_with_keymap( &mut self, menu_name: String, + description: String, keymap: Keymap, ) -> Result<(), SynlessError> { let doc_name = self.engine.visible_doc_name(); - self.layers.open_menu(doc_name, menu_name, Some(keymap)) + self.layers + .open_menu(doc_name, menu_name, description, Some(keymap)) } pub fn close_menu(&mut self) { @@ -132,7 +144,12 @@ impl + 'static> Runtime { .map_err(|err| error!(Frontend, "{}", err))?; let get_content = |doc_label| self.engine.get_content(doc_label); - pane::display_pane(&mut self.frontend, &self.pane_notation, &get_content)?; + pane::display_pane( + &mut self.frontend, + &self.pane_notation, + &Style::default(), + &get_content, + )?; self.frontend .end_frame() @@ -140,7 +157,11 @@ impl + 'static> Runtime { } fn update_auxilliary_docs(&mut self) { - for (name, node) in [self.make_keyhint_doc(), self.make_candidate_selection_doc()] { + for (name, node) in [ + self.make_keyhint_doc(), + self.make_candidate_selection_doc(), + self.make_menu_name_doc(), + ] { let _ = self.engine.delete_doc(&name); if let Some(node) = node { self.engine.add_doc(&name, node).bug(); @@ -152,7 +173,7 @@ impl + 'static> Runtime { let storage = self.engine.raw_storage_mut(); let node = self.layers.make_candidate_selection_doc(storage); ( - DocName::Auxilliary(CANDIDATE_SELECTION_DOC_NAME.to_owned()), + DocName::Auxilliary(CANDIDATE_SELECTION_DOC_LABEL.to_owned()), node, ) } @@ -168,7 +189,15 @@ impl + 'static> Runtime { let node = self .layers .make_keyhint_doc(storage, mode, visible_doc_name.as_ref()); - (DocName::Auxilliary(KEYHINTS_DOC_NAME.to_owned()), node) + (DocName::Auxilliary(KEYHINTS_DOC_LABEL.to_owned()), node) + } + + fn make_menu_name_doc(&mut self) -> (DocName, Option) { + let opt_node = self + .layers + .menu_description() + .map(|menu_name| self.engine.make_string_doc(menu_name.to_owned())); + (DocName::Auxilliary(MENU_NAME_LABEL.to_owned()), opt_node) } /****************** @@ -357,33 +386,45 @@ fn make_pane_notation(_include_menu: bool) -> pane::PaneNotation Result { fn path_file_name(path: &str) -> Result { use std::path::Path; + let os_str = Path::new(path) .file_name() .ok_or_else(|| error!(FileSystem, "Path ends in `..`: {path}"))?; @@ -448,6 +490,17 @@ fn path_file_name(path: &str) -> Result { .into()) } +fn canonicalize_path(path: &str) -> Result { + use std::path::Path; + + Ok(Path::new(path) + .canonicalize() + .map_err(|_| error!(FileSystem, "Invalid path: {path}"))? + .to_str() + .ok_or_else(|| error!(FileSystem, "Path is not valid unicode: {path}"))? + .into()) +} + macro_rules! register { ($module:expr, $runtime:ident . $method:ident($( $param:ident : $type:ty ),*)) => { register!($module, $runtime . $method($( $param : $type ),*) as $method) @@ -531,8 +584,12 @@ impl + 'static> Runtime { register!(module, rt.register_layer(layer: Layer)); register!(module, rt.add_global_layer(layer_name: &str)?); register!(module, rt.remove_global_layer(layer_name: &str)?); - register!(module, rt.open_menu(menu_name: String)?); - register!(module, rt.open_menu_with_keymap(menu_name: String, keymap: Keymap)? as open_menu); + register!(module, rt.open_menu(menu_name: String, description: String)?); + register!(module, rt.open_menu_with_keymap( + menu_name: String, + description: String, + keymap: Keymap + )? as open_menu); register!(module, rt.close_menu()); register!(module, escape()?); register!(module, rt.menu_selection_up()?); @@ -542,6 +599,7 @@ impl + 'static> Runtime { // Filesystem register!(module, list_files_and_dirs(dir: &str)?); register!(module, path_file_name(path: &str)?); + register!(module, canonicalize_path(path: &str)?); // Doc management register!(module, rt.current_dir()?); diff --git a/src/util/fuzzy_search.rs b/src/util/fuzzy_search.rs index 2579478..43dca90 100644 --- a/src/util/fuzzy_search.rs +++ b/src/util/fuzzy_search.rs @@ -36,7 +36,7 @@ struct Searcher(Vec); impl Searcher { fn new(input: &str) -> Result { let pattern = input - .split(" ") + .split(' ') .map(regex::escape) .collect::>() .join(".*"); diff --git a/tests/tests.rs b/tests/tests.rs index 427b1b3..aab83e5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ use partial_pretty_printer as ppp; use synless::{ - AritySpec, ConstructSpec, DocRef, GrammarSpec, LanguageSpec, Location, Node, NotationSetSpec, - SortSpec, Storage, + AritySpec, ConstructSpec, DocRef, GrammarSpec, LanguageSpec, Node, NotationSetSpec, SortSpec, + Storage, }; // e.g. example.com?p1=v1,p2=v2,p3,p4=v4 @@ -120,7 +120,7 @@ fn test_doc_ref() { let params = node_with_children(&mut s, "urllang", "Params", [eq_1, eq_2, done]); let url = node_with_children(&mut s, "urllang", "Url", [domain, params]); - let doc_ref = DocRef::new_display(&s, Location::at(&s, url), url); + let doc_ref = DocRef::new_display(&s, None, url); let actual = match ppp::pretty_print_to_string(doc_ref, 80) { Ok(actual) => actual,