From e632f6c84978b6f43f850b4fa80c56e2ef003d2a Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 19 Apr 2024 14:19:52 -0400 Subject: [PATCH] chore: delete cruft --- demo/Cargo.toml | 12 - demo/src/data/example_keymaps.rs | 200 ------- demo/src/data/example_pane_notation.rs | 66 --- demo/src/data/keyhint_lang.rs | 61 --- demo/src/data/message_lang.rs | 32 -- demo/src/data/mod.rs | 7 - demo/src/engine.rs | 266 --------- demo/src/error.rs | 86 --- demo/src/keymaps/keymap.rs | 97 ---- demo/src/keymaps/keymap_manager.rs | 180 ------ demo/src/keymaps/mod.rs | 7 - demo/src/keymaps/mode_and_menu.rs | 140 ----- demo/src/main.rs | 27 - demo/src/prog.rs | 291 ---------- demo/src/server.rs | 279 ---------- demo/src/test_multilang_doc.rs | 104 ---- editor/Cargo.toml | 22 - editor/benches/render_benchmark.rs | 59 -- editor/src/ast/ast.rs | 441 --------------- editor/src/ast/ast_forest.rs | 159 ------ editor/src/ast/ast_ref.rs | 122 ----- editor/src/ast/mod.rs | 10 - editor/src/command.rs | 126 ----- editor/src/doc.rs | 624 --------------------- editor/src/lib.rs | 16 - editor/src/notationset.rs | 175 ------ editor/src/test_util/json_lang.rs | 143 ----- editor/src/test_util/mod.rs | 5 - editor/src/test_util/test_editor.rs | 101 ---- editor/src/text.rs | 253 --------- editor/tests/test_json_doc.rs | 732 ------------------------- experiment/experimental_tree.rs | 270 --------- experiment/gom.rs | 111 ---- experiment/language.rs | 151 ----- experiment/mod.rs | 0 experiment/tree/cursor.rs | 503 ----------------- experiment/tree/mod.rs | 12 - experiment/tree/path.rs | 47 -- experiment/tree/tree.rs | 335 ----------- experiment/tree/tree_mut.rs | 166 ------ experiment/tree/tree_ref.rs | 127 ----- forest/Cargo.toml | 9 - forest/src/lib.rs | 631 --------------------- frontends/Cargo.toml | 12 - frontends/src/color_theme.rs | 141 ----- frontends/src/frontend.rs | 32 -- frontends/src/key.rs | 50 -- frontends/src/lib.rs | 9 - frontends/src/terminal.rs | 210 ------- frontends/src/terminal/screen_buf.rs | 619 --------------------- frontends/src/terminal/term_error.rs | 13 - language/Cargo.toml | 12 - language/src/ast/ast.rs | 256 --------- language/src/ast/ast_forest.rs | 81 --- language/src/ast/ast_ref.rs | 78 --- language/src/ast/mod.rs | 9 - language/src/ast/text.rs | 252 --------- language/src/language/construct.rs | 95 ---- language/src/language/language_set.rs | 248 --------- language/src/language/mod.rs | 5 - language/src/lib.rs | 5 - old-stuff/doc/mod.rs | 7 - old-stuff/editor/command.rs | 45 -- old-stuff/editor/editor.rs | 167 ------ old-stuff/editor/keymap.rs | 129 ----- old-stuff/editor/mod.rs | 9 - old-stuff/main.rs | 21 - old-stuff/render/locate_cursor.rs | 118 ---- old-stuff/render/mod.rs | 42 -- old-stuff/render/position_screen.rs | 79 --- old-stuff/render/render.rs | 125 ----- old-stuff/render/write.rs | 52 -- old-stuff/tree/cursor.rs | 491 ----------------- old-stuff/tree/mod.rs | 14 - old-stuff/tree/node.rs | 110 ---- old-stuff/tree/path.rs | 57 -- old-stuff/tree/tree.rs | 339 ------------ old-stuff/tree/tree_mut.rs | 163 ------ old-stuff/tree/tree_ref.rs | 126 ----- 79 files changed, 11426 deletions(-) delete mode 100644 demo/Cargo.toml delete mode 100644 demo/src/data/example_keymaps.rs delete mode 100644 demo/src/data/example_pane_notation.rs delete mode 100644 demo/src/data/keyhint_lang.rs delete mode 100644 demo/src/data/message_lang.rs delete mode 100644 demo/src/data/mod.rs delete mode 100644 demo/src/engine.rs delete mode 100644 demo/src/error.rs delete mode 100644 demo/src/keymaps/keymap.rs delete mode 100644 demo/src/keymaps/keymap_manager.rs delete mode 100644 demo/src/keymaps/mod.rs delete mode 100644 demo/src/keymaps/mode_and_menu.rs delete mode 100644 demo/src/main.rs delete mode 100644 demo/src/prog.rs delete mode 100644 demo/src/server.rs delete mode 100644 demo/src/test_multilang_doc.rs delete mode 100644 editor/Cargo.toml delete mode 100644 editor/benches/render_benchmark.rs delete mode 100644 editor/src/ast/ast.rs delete mode 100644 editor/src/ast/ast_forest.rs delete mode 100644 editor/src/ast/ast_ref.rs delete mode 100644 editor/src/ast/mod.rs delete mode 100644 editor/src/command.rs delete mode 100644 editor/src/doc.rs delete mode 100644 editor/src/lib.rs delete mode 100644 editor/src/notationset.rs delete mode 100644 editor/src/test_util/json_lang.rs delete mode 100644 editor/src/test_util/mod.rs delete mode 100644 editor/src/test_util/test_editor.rs delete mode 100644 editor/src/text.rs delete mode 100644 editor/tests/test_json_doc.rs delete mode 100644 experiment/experimental_tree.rs delete mode 100644 experiment/gom.rs delete mode 100644 experiment/language.rs delete mode 100644 experiment/mod.rs delete mode 100644 experiment/tree/cursor.rs delete mode 100644 experiment/tree/mod.rs delete mode 100644 experiment/tree/path.rs delete mode 100644 experiment/tree/tree.rs delete mode 100644 experiment/tree/tree_mut.rs delete mode 100644 experiment/tree/tree_ref.rs delete mode 100644 forest/Cargo.toml delete mode 100644 forest/src/lib.rs delete mode 100644 frontends/Cargo.toml delete mode 100644 frontends/src/color_theme.rs delete mode 100644 frontends/src/frontend.rs delete mode 100644 frontends/src/key.rs delete mode 100644 frontends/src/lib.rs delete mode 100644 frontends/src/terminal.rs delete mode 100644 frontends/src/terminal/screen_buf.rs delete mode 100644 frontends/src/terminal/term_error.rs delete mode 100644 language/Cargo.toml delete mode 100644 language/src/ast/ast.rs delete mode 100644 language/src/ast/ast_forest.rs delete mode 100644 language/src/ast/ast_ref.rs delete mode 100644 language/src/ast/mod.rs delete mode 100644 language/src/ast/text.rs delete mode 100644 language/src/language/construct.rs delete mode 100644 language/src/language/language_set.rs delete mode 100644 language/src/language/mod.rs delete mode 100644 language/src/lib.rs delete mode 100644 old-stuff/doc/mod.rs delete mode 100644 old-stuff/editor/command.rs delete mode 100644 old-stuff/editor/editor.rs delete mode 100644 old-stuff/editor/keymap.rs delete mode 100644 old-stuff/editor/mod.rs delete mode 100644 old-stuff/main.rs delete mode 100644 old-stuff/render/locate_cursor.rs delete mode 100644 old-stuff/render/mod.rs delete mode 100644 old-stuff/render/position_screen.rs delete mode 100644 old-stuff/render/render.rs delete mode 100644 old-stuff/render/write.rs delete mode 100644 old-stuff/tree/cursor.rs delete mode 100644 old-stuff/tree/mod.rs delete mode 100644 old-stuff/tree/node.rs delete mode 100644 old-stuff/tree/path.rs delete mode 100644 old-stuff/tree/tree.rs delete mode 100644 old-stuff/tree/tree_mut.rs delete mode 100644 old-stuff/tree/tree_ref.rs diff --git a/demo/Cargo.toml b/demo/Cargo.toml deleted file mode 100644 index 6da11e2..0000000 --- a/demo/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "demo" -version = "0.1.0" -authors = ["e-matteson "] -edition = "2018" - -[dependencies] -editor = { path = "../editor" } -forest = { path = "../forest" } -language = { path = "../language" } -frontends = { path = "../frontends"} -thiserror = "1.0" diff --git a/demo/src/data/example_keymaps.rs b/demo/src/data/example_keymaps.rs deleted file mode 100644 index 79dc86a..0000000 --- a/demo/src/data/example_keymaps.rs +++ /dev/null @@ -1,200 +0,0 @@ -use frontends::Key; -use std::iter; - -use language::{ArityType, Language, LanguageName}; - -use crate::keymaps::{FilterRule, TextKeymap, TreeKeymap}; -use crate::prog::{Prog, Value, Word}; - -/// Construct a keymap used for selecting a type of node, eg. for the Replace -/// command. -pub fn make_node_map<'l>(lang: &Language) -> TreeKeymap<'l> { - TreeKeymap::new( - lang.keymap() - .iter() - .map(|(&ch, construct_name)| { - ( - Key::Char(ch), - FilterRule::Sort(lang.lookup_construct(construct_name).sort.clone()), - Prog::new(vec![ - Word::Literal(Value::LangConstruct( - lang.name().to_owned(), - construct_name.to_owned(), - )), - Word::NodeByName, - Word::Replace, - ]) - .with_name(String::from(construct_name)), - ) - }) - .chain(iter::once(( - Key::Esc, - FilterRule::Always, - Prog::new(vec![ - Word::Literal(Value::String("Cancelled node replacement!".into())), - Word::Print, - ]) - .with_name("Cancel"), - ))) - .collect(), - ) -} - -/// Construct a keymap used for navigating and editing a document that's in tree -/// mode. -pub fn make_tree_map<'l>() -> TreeKeymap<'l> { - TreeKeymap::new(vec![ - ( - Key::Char('d'), - FilterRule::Always, - Prog::new_single(Word::Cut), - ), - ( - Key::Char('y'), - FilterRule::Always, - Prog::new_single(Word::Copy), - ), - ( - Key::Char('P'), - FilterRule::Always, - Prog::new_single(Word::PasteSwap), - ), - ( - Key::Char('p'), - FilterRule::Always, - Prog::new(vec![Word::PasteSwap, Word::PopClipboard]).with_name("PasteReplace"), - ), - ( - Key::Char('u'), - FilterRule::Always, - Prog::new_single(Word::Undo), - ), - ( - Key::Ctrl('r'), - FilterRule::Always, - Prog::new_single(Word::Redo), - ), - ( - Key::Right, - FilterRule::Always, - Prog::new_single(Word::Right), - ), - (Key::Left, FilterRule::Always, Prog::new_single(Word::Left)), - (Key::Up, FilterRule::Always, Prog::new_single(Word::Parent)), - ( - Key::Backspace, - FilterRule::ParentArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new_single(Word::Remove), - ), - ( - Key::Char('x'), - FilterRule::Always, - Prog::new_single(Word::Clear), - ), - ( - Key::Down, - FilterRule::Always, - Prog::new(vec![Word::Literal(Value::Usize(0)), Word::Child]).with_name("Child"), - ), - ( - Key::Char('i'), - FilterRule::ParentArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new_single(Word::InsertHoleAfter), - ), - ( - Key::Char('I'), - FilterRule::ParentArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new_single(Word::InsertHoleBefore), - ), - ( - Key::Char('o'), - FilterRule::SelfArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new_single(Word::InsertHolePostpend), - ), - ( - Key::Char('O'), - FilterRule::SelfArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new_single(Word::InsertHolePrepend), - ), - ( - Key::Char('r'), - FilterRule::Always, - Prog::new(vec![ - Word::Literal(Prog::from(Word::Replace).quote()), - Word::Literal(Value::MenuName("node".into())), - Word::ActivateMenu, - ]) - .with_name("Replace"), - ), - ( - Key::Char(' '), - FilterRule::ParentArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new(vec![ - Word::Literal(Value::ModeName("speed_bool".into())), - Word::PushMode, - ]) - .with_name("SpeedBoolMode"), - ), - ( - Key::Char('m'), - FilterRule::Always, - Prog::new(vec![Word::Literal(Value::Char('m')), Word::SetBookmark]).with_name("Mark"), - ), - ( - Key::Char('\''), - FilterRule::Always, - Prog::new(vec![Word::Literal(Value::Char('m')), Word::GotoBookmark]) - .with_name("GotoMark"), - ), - ]) -} - -/// Construct a keymap for a silly `speed-bool` mode that demonstrates -/// persistent mode-style keymaps. It lets you insert new `true` or `false` -/// nodes into a list by repeatedly pressing a single key. -pub fn make_speed_bool_map<'l>() -> TreeKeymap<'l> { - let lang: LanguageName = "json".into(); - TreeKeymap::new(vec![ - ( - Key::Char('t'), - FilterRule::ParentArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new(vec![ - Word::Literal(Value::LangConstruct(lang.clone(), "true".into())), - Word::InsertHoleAfter, - Word::NodeByName, - Word::Replace, - ]) - .with_name("True"), - ), - ( - Key::Char('f'), - FilterRule::ParentArity(vec![ArityType::Flexible, ArityType::Mixed]), - Prog::new(vec![ - Word::Literal(Value::LangConstruct(lang, "false".into())), - Word::InsertHoleAfter, - Word::NodeByName, - Word::Replace, - ]) - .with_name("False"), - ), - ( - Key::Esc, - FilterRule::Always, - Prog::new(vec![Word::PopMode]).with_name("Exit"), - ), - ]) -} - -/// Make a keymap used for editing texty nodes. -pub fn make_text_map<'l>() -> TextKeymap<'l> { - let bindings = vec![ - (Key::Esc, Prog::new_single(Word::TreeMode)), - (Key::Up, Prog::new_single(Word::TreeMode)), - (Key::Left, Prog::new_single(Word::TextLeft)), - (Key::Right, Prog::new_single(Word::TextRight)), - (Key::Backspace, Prog::new_single(Word::DeleteCharBackward)), - (Key::Delete, Prog::new_single(Word::DeleteCharForward)), - ]; - - TextKeymap::new(bindings.into_iter().collect()) -} diff --git a/demo/src/data/example_pane_notation.rs b/demo/src/data/example_pane_notation.rs deleted file mode 100644 index c71c7ed..0000000 --- a/demo/src/data/example_pane_notation.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::engine::DocLabel; -use pretty::{ - Color, CursorVisibility, PaneNotation, PaneSize, RenderOptions, ScrollStrategy, Style, - WidthStrategy, -}; - -pub fn make_example_pane_notation() -> PaneNotation { - let active = PaneNotation::Doc { - label: DocLabel::ActiveDoc, - render_options: RenderOptions { - cursor_visibility: CursorVisibility::Show, - scroll_strategy: ScrollStrategy::CursorHeight { fraction: 0.6 }, - width_strategy: WidthStrategy::Full, - }, - }; - - let key_hints_name = PaneNotation::Doc { - label: DocLabel::KeymapName, - render_options: RenderOptions { - cursor_visibility: CursorVisibility::Hide, - scroll_strategy: ScrollStrategy::Beginning, - width_strategy: WidthStrategy::Full, - }, - }; - - let key_hints = PaneNotation::Doc { - label: DocLabel::KeyHints, - render_options: RenderOptions { - cursor_visibility: CursorVisibility::Hide, - scroll_strategy: ScrollStrategy::Beginning, - width_strategy: WidthStrategy::Full, - }, - }; - - let messages = PaneNotation::Doc { - label: DocLabel::Messages, - render_options: RenderOptions { - cursor_visibility: CursorVisibility::Hide, - scroll_strategy: ScrollStrategy::Beginning, - width_strategy: WidthStrategy::Full, - }, - }; - - let divider = PaneNotation::Fill { - ch: '=', - style: Style::color(Color::Base03), - }; - - let status_bar = PaneNotation::Horz { - panes: vec![ - (PaneSize::Proportional(1), divider.clone()), - (PaneSize::Proportional(1), key_hints_name), - (PaneSize::Proportional(1), divider.clone()), - ], - }; - - PaneNotation::Vert { - panes: vec![ - (PaneSize::Proportional(1), active), - (PaneSize::Fixed(1), status_bar), - (PaneSize::DynHeight, key_hints), - (PaneSize::Fixed(1), divider), - (PaneSize::Fixed(5), messages), - ], - } -} diff --git a/demo/src/data/keyhint_lang.rs b/demo/src/data/keyhint_lang.rs deleted file mode 100644 index 7f84a4a..0000000 --- a/demo/src/data/keyhint_lang.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::NotationSet; -use language::{Arity, Construct, Language}; -use pretty::notation_constructors::{child, literal, no_wrap, repeat, text}; -use pretty::{Color, Notation, RepeatInner, Style}; - -pub fn make_keyhint_lang() -> (Language, NotationSet) { - let notations = vec![ - ("key".into(), text(Style::color(Color::Base0D))), - ("prog".into(), text(Style::color(Color::Base04))), - ("binding".into(), binding()), - ("keymap".into(), keymap()), - ]; - let constructs = vec![ - Construct::new("key", "Key", Arity::Text, Some('k')), - Construct::new("prog", "Value", Arity::Text, Some('p')), - Construct::new( - "keymap", - "Keymap", - Arity::Flexible("Binding".into()), - Some('d'), - ), - Construct::new( - "binding", - "Binding", - Arity::Fixed(vec!["Key".into(), "Value".into()]), - Some('e'), - ), - ]; - // TODO: some of this boilerplate should get abstracted out - let mut lang = Language::new("keyhint".into()); - for construct in constructs { - lang.add(construct); - } - let note_set = NotationSet::new(&lang, notations); - (lang, note_set) -} - -/// Try putting the key and value on the same line. -/// If they don't fit, wrap after the colon, and indent the value. -fn binding() -> Notation { - no_wrap(child(0) + punct(":") + child(1)) | ((child(0) + punct(":")) ^ (indent() + child(1))) -} - -/// Wrap entries tightly. -fn keymap() -> Notation { - repeat(RepeatInner { - empty: punct("(empty keyhints)"), - lone: child(0), - join: (Notation::Left + punct(", ") + no_wrap(Notation::Right)) - | (Notation::Left + punct(",")) ^ no_wrap(Notation::Right), - surround: Notation::Surrounded, - }) -} - -fn punct(text: &str) -> Notation { - literal(text, Style::color(Color::Base03)) -} - -fn indent() -> Notation { - literal(" ", Style::plain()) -} diff --git a/demo/src/data/message_lang.rs b/demo/src/data/message_lang.rs deleted file mode 100644 index b916e58..0000000 --- a/demo/src/data/message_lang.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::NotationSet; -use language::{Arity, Construct, Language}; -use pretty::notation_constructors::{child, literal, repeat, text}; -use pretty::{Color, Notation, RepeatInner, Style}; - -pub fn make_message_lang() -> (Language, NotationSet) { - let notations = vec![ - ("message".into(), text(Style::color(Color::Base08))), - ("list".into(), list()), - ]; - let constructs = vec![ - Construct::new("message", "Message", Arity::Text, None), - Construct::new("list", "List", Arity::Flexible("Message".into()), None), - ]; - // TODO: some of this boilerplate should get abstracted out - let mut lang = Language::new("message".into()); - for construct in constructs { - lang.add(construct); - } - let note_set = NotationSet::new(&lang, notations); - (lang, note_set) -} - -/// Put all messages on separate lines -fn list() -> Notation { - repeat(RepeatInner { - empty: literal("", Style::plain()), - lone: child(0), - join: Notation::Left ^ Notation::Right, - surround: Notation::Surrounded, - }) -} diff --git a/demo/src/data/mod.rs b/demo/src/data/mod.rs deleted file mode 100644 index 9ed7a0c..0000000 --- a/demo/src/data/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// TODO All these things should eventually be loaded from tree files, not -// hardcoded in rust. - -pub mod example_keymaps; -pub mod example_pane_notation; -pub mod keyhint_lang; -pub mod message_lang; diff --git a/demo/src/engine.rs b/demo/src/engine.rs deleted file mode 100644 index c8f55f4..0000000 --- a/demo/src/engine.rs +++ /dev/null @@ -1,266 +0,0 @@ -use std::collections::HashMap; -use std::fmt::{self, Debug}; - -use editor::{ - Ast, AstForest, AstRef, Clipboard, Doc, MetaCommand, NotationSet, NotationSets, TreeCmd, - TreeNavCmd, -}; -use forest::Bookmark; -use frontends::Frontend; -use language::{ConstructName, Language, LanguageName, LanguageSet}; -use pretty::{Pane, PaneNotation}; - -use crate::error::EngineError; - -/// A set of standard document labels that can be referenced in `PaneNotation`s. -/// Every time `Pane.render()` is called, it will dynamically look up the document that is currently -/// associated with each referenced label. -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -#[non_exhaustive] -#[allow(dead_code)] -pub enum DocLabel { - /// The document that currently has focus / is being actively edited. - ActiveDoc, - /// The name/title of the `ActiveDoc`, eg. for showing in a status bar. - ActiveDocName, - /// Information about what key bindings are available in the current keymap and context. - KeyHints, - /// The name of the current keymap. - KeymapName, - /// Messages to the user. - Messages, -} - -impl fmt::Display for DocLabel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -/// This assumes that there is a single language associated with each Doc. That -/// might not be true forever! But it's a huge pain to create new nodes for the -/// Doc if we don't know what language to use. -struct DocEntry<'l> { - doc: Doc<'l>, - lang_name: LanguageName, -} - -struct Docs<'l>(HashMap>); - -impl<'l> Docs<'l> { - fn new() -> Self { - Self(HashMap::new()) - } - - fn insert(&mut self, label: DocLabel, doc: Doc<'l>, lang_name: LanguageName) { - self.0.insert(label, DocEntry { doc, lang_name }); - } - - fn get_doc(&self, label: &DocLabel) -> Option<&Doc<'l>> { - self.0.get(label).map(|entry| &entry.doc) - } - - fn get_doc_mut(&mut self, label: &DocLabel) -> Option<&mut Doc<'l>> { - self.0.get_mut(label).map(|entry| &mut entry.doc) - } - - fn get_lang_name(&self, label: &DocLabel) -> Option<&LanguageName> { - self.0.get(label).map(|entry| &entry.lang_name) - } - - fn get_ast_ref<'a>(&'a self, label: &DocLabel) -> Option> { - self.get_doc(label).map(|doc| (doc.ast_ref())) - } -} - -pub struct Engine<'l> { - docs: Docs<'l>, - forest: AstForest<'l>, - bookmarks: HashMap, - cut_stack: Clipboard<'l>, - pane_notation: PaneNotation, - language_set: &'l LanguageSet, - notation_sets: &'l NotationSets, -} - -impl<'l> Engine<'l> { - pub fn new( - language_set: &'l LanguageSet, - notation_sets: &'l NotationSets, - pane_notation: PaneNotation, - keyhint_lang: (Language, NotationSet), - message_lang: (Language, NotationSet), - active_lang: (Language, NotationSet), - ) -> Result { - let mut engine = Engine { - docs: Docs::new(), - forest: AstForest::new(language_set), - cut_stack: Clipboard::new(), - bookmarks: HashMap::new(), - pane_notation, - language_set, - notation_sets, - }; - let keyhint_lang_name = engine.register_language(keyhint_lang); - let message_lang_name = engine.register_language(message_lang); - let active_lang_name = engine.register_language(active_lang); - - engine.new_doc(DocLabel::KeyHints, "KeyHints", keyhint_lang_name)?; - engine.new_doc( - DocLabel::KeymapName, - "KeymapName", - message_lang_name.clone(), - )?; - engine.new_doc(DocLabel::ActiveDoc, "DemoDoc", active_lang_name)?; - engine.new_doc(DocLabel::Messages, "Messages", message_lang_name)?; - engine.clear_messages()?; - Ok(engine) - } - - pub fn register_language(&mut self, lang_info: (Language, NotationSet)) -> LanguageName { - let (lang, notation_set) = lang_info; - let name = lang.name().to_owned(); - self.language_set.insert(name.clone(), lang); - self.notation_sets.insert(name.clone(), notation_set); - name - } - - pub fn active_doc(&self) -> Result<&Doc<'l>, EngineError> { - self.docs - .get_doc(&DocLabel::ActiveDoc) - .ok_or_else(|| EngineError::UnknownDocLabel(DocLabel::ActiveDoc)) - } - - pub fn lang_name_of<'a>(&'a self, label: &DocLabel) -> Result<&'a LanguageName, EngineError> { - self.docs - .get_lang_name(label) - .ok_or_else(|| EngineError::UnknownDocLabel(label.to_owned())) - } - - pub fn language(&self, lang_name: &LanguageName) -> Result<&'l Language, EngineError> { - self.language_set - .get(lang_name) - .ok_or_else(|| EngineError::UnknownLang(lang_name.to_owned())) - } - - fn notation_set(&self, lang_name: &LanguageName) -> Result<&'l NotationSet, EngineError> { - self.notation_sets - .get(lang_name) - .ok_or_else(|| EngineError::UnknownLang(lang_name.to_owned())) - } - - pub fn show_message(&mut self, msg: &str) -> Result<(), EngineError> { - let mut msg_node = self.new_node_in_doc_lang(&"message".into(), &DocLabel::Messages)?; - msg_node.inner().unwrap_text().text_mut(|t| { - t.activate(); - t.set(msg.to_owned()); - t.deactivate(); - }); - self.exec_on(TreeCmd::InsertHolePrepend, &DocLabel::Messages)?; - self.exec_on(TreeCmd::Replace(msg_node), &DocLabel::Messages)?; - self.exec_on(TreeNavCmd::Parent, &DocLabel::Messages)?; - Ok(()) - } - - pub fn clear_messages(&mut self) -> Result<(), EngineError> { - self.exec_on( - TreeCmd::Replace(self.new_node_in_doc_lang(&"list".into(), &DocLabel::Messages)?), - &DocLabel::Messages, - ) - } - - pub fn redisplay(&self, frontend: &mut F) -> Result<(), EngineError> - where - F: Frontend, - { - frontend.draw_frame(|mut pane: Pane| { - pane.render(&self.pane_notation, |label: &DocLabel| { - self.docs.get_ast_ref(label) - }) - })?; - Ok(()) - } - - /// Create a new node in the same language as the given doc. - pub fn new_node_in_doc_lang( - &self, - construct_name: &ConstructName, - doc_label: &DocLabel, - ) -> Result, EngineError> { - self.new_node(construct_name, self.lang_name_of(doc_label)?) - } - - pub fn new_node( - &self, - construct_name: &ConstructName, - lang_name: &LanguageName, - ) -> Result, EngineError> { - let lang = self.language(lang_name)?; - let notes = self.notation_set(lang_name)?; - - self.forest - .new_tree(lang, construct_name, notes) - .ok_or_else(|| EngineError::UnknownConstruct { - construct: construct_name.to_owned(), - lang: lang_name.to_owned(), - }) - } - - pub fn exec(&mut self, cmd: T) -> Result<(), EngineError> - where - T: Debug + Into>, - { - self.exec_on(cmd.into(), &DocLabel::ActiveDoc) - } - - pub fn exec_on(&mut self, cmd: T, doc_label: &DocLabel) -> Result<(), EngineError> - where - T: Debug + Into>, - { - self.docs - .get_doc_mut(doc_label) - .ok_or_else(|| EngineError::UnknownDocLabel(doc_label.to_owned()))? - .execute(cmd.into(), &mut self.cut_stack)?; - Ok(()) - } - - pub fn add_bookmark(&mut self, name: char, doc_label: &DocLabel) -> Result<(), EngineError> { - let mark = self - .docs - .get_doc_mut(doc_label) - .ok_or_else(|| EngineError::UnknownDocLabel(doc_label.to_owned()))? - .bookmark(); - self.bookmarks.insert(name, mark); - Ok(()) - } - - pub fn get_bookmark(&mut self, name: char) -> Result { - // TODO handle bookmarks into multiple documents - self.bookmarks - .get(&name) - .cloned() - .ok_or(EngineError::UnknownBookmark) - } - - /// Create and store a new document. The document will consist of a root - /// with a hole as its child, and the cursor will be positioned on the hole. - fn new_doc( - &mut self, - label: DocLabel, - doc_name: &str, - lang_name: LanguageName, - ) -> Result<(), EngineError> { - let mut root_node = self.new_node(&"root".into(), &lang_name)?; - let hole = root_node.new_hole(); - root_node - .inner() - .unwrap_fixed() - .replace_child(0, hole) - .unwrap(); - root_node.inner().unwrap_fixed().goto_child(0); - - self.docs - .insert(label, Doc::new(doc_name, root_node), lang_name); - Ok(()) - } -} diff --git a/demo/src/error.rs b/demo/src/error.rs deleted file mode 100644 index 3214788..0000000 --- a/demo/src/error.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::io; - -use editor::DocError; -use frontends::{terminal::TermError, Key}; -use language::{ConstructName, LanguageName}; -use pretty::PaneError; - -use crate::engine::DocLabel; -use crate::keymaps::{MenuName, ModeName}; - -#[derive(thiserror::Error, Debug)] -pub enum ServerError { - #[error("not in keymap: {0:?}")] - UnknownKey(Key), - - #[error("unknown keymap mode: {0:?}")] - UnknownModeName(ModeName), - - #[error("unknown keymap menu: {0:?}")] - UnknownMenuName(MenuName), - - #[error("no keymap mode selected")] - NoMode, - - #[error("unknown user input event")] - UnknownEvent, - - #[error("received keyboard interrupt")] - KeyboardInterrupt, - - // TODO include actual type too - #[error("expected value of type {0} on data stack")] - ExpectedValue(String), - - #[error("data stack was unexpectedly empty")] - EmptyDataStack, - - #[error("input/output error: {0}")] - Io(#[from] io::Error), - - #[error("terminal error: {0}")] - Term(#[from] TermError), - - // Note: we can't use the inner EngineError as the error source because it - // contains a non-static lifetime. - #[error("engine error: {0}")] - Engine(EngineError), -} - -impl From for ServerError { - fn from(e: EngineError) -> ServerError { - ServerError::Engine(e) - } -} - -#[derive(thiserror::Error, Debug)] -pub enum EngineError { - #[error("unknown language: {0}")] - UnknownLang(LanguageName), - - #[error("language {lang} does not contain construct {construct}")] - UnknownConstruct { - construct: ConstructName, - lang: LanguageName, - }, - - #[error("no bookmark stored with this key")] - UnknownBookmark, - - #[error("no document with label {0:?}")] - UnknownDocLabel(DocLabel), - - #[error("pane error: {0}")] - Pane(#[from] PaneError), - - // Note: we can't use the inner DocError as the error source because it - // contains a non-static lifetime. - #[error("doc error: {0}")] - DocExec(DocError), -} - -impl From for EngineError { - fn from(e: DocError) -> EngineError { - EngineError::DocExec(e) - } -} diff --git a/demo/src/keymaps/keymap.rs b/demo/src/keymaps/keymap.rs deleted file mode 100644 index dbb9380..0000000 --- a/demo/src/keymaps/keymap.rs +++ /dev/null @@ -1,97 +0,0 @@ -use frontends::Key; -use std::collections::HashMap; - -use crate::prog::Prog; -use language::{ArityType, Sort}; - -/// Stores all the keybindings of a text-keymap, except for keys that just -/// insert themselves as literal characters. -pub struct TextKeymap<'l>(HashMap>); - -/// Stores all the keybindings of a tree-keymap, along with rules for which -/// keybindings should be available in which contexts. -pub struct TreeKeymap<'l>(HashMap)>); - -/// Rules for when a particular item should be included in a keymap. -#[derive(Clone, Debug)] -pub enum FilterRule { - /// Unconditionally include the item. - Always, - /// Only include the item if the given `Sort` is acceptable in the current position. - Sort(Sort), - /// Only include the item if the arity-type of the current node's parent is contained in the given list. - ParentArity(Vec), - /// Only include the item if the arity-type of the current node is contained in the given list. - SelfArity(Vec), -} - -/// Information needed to apply a FilterRule, based on the context within a -/// document. -pub struct FilterContext { - /// The Sort that any node in the current node's position is required to have. - pub sort: Sort, - /// The arity of the current node's parent. - pub parent_arity: ArityType, - /// The arity of the current node. - pub self_arity: ArityType, -} - -impl<'l> TextKeymap<'l> { - pub fn new(non_literal_keys: HashMap>) -> Self { - Self(non_literal_keys) - } - - pub(super) fn empty() -> Self { - Self(HashMap::new()) - } - - pub(super) fn get(&self, key: Key) -> Option<&'_ Prog<'l>> { - self.0.get(&key) - } - - pub(super) fn keys_and_names<'a>(&'a self) -> Vec<(Key, Option<&'a str>)> { - self.0 - .iter() - .map(|(&key, prog)| (key, prog.name())) - .collect() - } -} - -impl<'l> TreeKeymap<'l> { - pub fn new(v: Vec<(Key, FilterRule, Prog<'l>)>) -> Self { - Self( - v.into_iter() - .map(|(key, filter, prog)| (key, (filter, prog))) - .collect(), - ) - } - - pub(super) fn get<'a>(&'a self, key: Key) -> Option<&'a Prog<'l>> { - self.0.get(&key).map(|(_filter, prog)| prog) - } - - pub(super) fn filter<'a>(&'a self, context: &FilterContext) -> Vec { - self.0 - .iter() - .filter_map(|(&key, (filter, _))| { - if filter.matches(context) { - Some(key) - } else { - None - } - }) - .collect() - } -} - -impl FilterRule { - /// True if an item with this filter rule should be included in this context. - fn matches(&self, context: &FilterContext) -> bool { - match self { - FilterRule::Always => true, - FilterRule::Sort(sort) => context.sort.accepts(sort), - FilterRule::ParentArity(arity_types) => arity_types.contains(&context.parent_arity), - FilterRule::SelfArity(arity_types) => arity_types.contains(&context.self_arity), - } - } -} diff --git a/demo/src/keymaps/keymap_manager.rs b/demo/src/keymaps/keymap_manager.rs deleted file mode 100644 index f242574..0000000 --- a/demo/src/keymaps/keymap_manager.rs +++ /dev/null @@ -1,180 +0,0 @@ -use frontends::Key; -use std::collections::HashMap; - -use crate::error::ServerError; -use crate::prog::{Prog, Value, Word}; - -use super::keymap::{FilterContext, TextKeymap, TreeKeymap}; -use super::mode_and_menu::{AvailableKeys, Menu, MenuName, Mode, ModeName}; - -/// Manage various forms of keymaps -pub struct KeymapManager<'l> { - /// The top of the stack is the current, persistent mode. Its keymap will - /// be used whenever the document is in tree-mode and there is no menu - /// active. - mode_stack: Vec, - /// If there is an active menu, its keymap will be used instead of the current mode's. - /// Menu's are meant to be shortlived (eg. deactivated after a single keypress). - active_menu: Option, - /// All known modes. - modes: HashMap>, - /// All known menus. - menus: HashMap>, - /// The one-and-only keymap used for entering text. Maybe we should allow - /// multiple text keymaps, someday. - text_keymap: TextKeymap<'l>, -} - -impl<'l> KeymapManager<'l> { - pub fn new() -> Self { - KeymapManager { - modes: HashMap::new(), - mode_stack: Vec::new(), - menus: HashMap::new(), - active_menu: None, - text_keymap: TextKeymap::empty(), - } - } - - /// Register a new mode for later use. - pub fn register_mode(&mut self, name: ModeName, keymap: TreeKeymap<'l>) { - self.modes.insert(name.clone(), Mode { keymap, name }); - } - - /// Register a new menu for later use. - pub fn register_menu(&mut self, name: MenuName, keymap: TreeKeymap<'l>) { - self.menus.insert(name.clone(), Menu { keymap, name }); - } - - /// Register a new text keymap for later use. Since we currently only - /// support one text keymap at a time, this replaces the existing one. - pub fn replace_text_keymap(&mut self, keymap: TextKeymap<'l>) { - self.text_keymap = keymap; - } - - /// Push this mode onto the stack, making it the current mode. Return an - /// error if the mode has not been registered. - pub fn push_mode(&mut self, name: ModeName) -> Result<(), ServerError> { - if self.modes.contains_key(&name) { - self.mode_stack.push(name); - Ok(()) - } else { - Err(ServerError::UnknownModeName(name)) - } - } - - /// Pop the mode stack, switching back to the previous mode. - pub fn pop_mode(&mut self) -> Option { - self.mode_stack.pop() - } - - /// Activate this menu, temporarily overriding the current mode until it's - /// deactivated. - pub fn activate_menu(&mut self, name: MenuName) -> Result<(), ServerError> { - if self.menus.contains_key(&name) { - self.active_menu = Some(name); - Ok(()) - } else { - Err(ServerError::UnknownMenuName(name)) - } - } - - /// Deactivate the active menu, if there is one. - pub fn deactivate_menu(&mut self) { - self.active_menu = None; - } - - /// True if there is an active menu overriding the current mode. - pub fn has_active_menu(&self) -> bool { - self.active_menu.is_some() - } - - /// Return the program that's mapped to this key in the given keymap, or None if the key isn't found. - pub fn lookup(&self, key: Key, available_keys: &AvailableKeys) -> Option> { - match available_keys { - AvailableKeys::Mode { - filtered_keys, - name, - } => { - if filtered_keys.contains(&key) { - self.modes.get(name).unwrap().get(key).cloned() - } else { - None - } - } - AvailableKeys::Menu { - filtered_keys, - name, - } => { - if filtered_keys.contains(&key) { - self.menus.get(name).unwrap().get(key).cloned() - } else { - None - } - } - AvailableKeys::Text => { - if let Some(prog) = self.text_keymap.get(key) { - Some(prog.to_owned()) - } else if let Key::Char(c) = key { - Some( - Prog::new(vec![Word::Literal(Value::Char(c)), Word::InsertChar]) - .with_name(c), - ) - } else { - None - } - } - } - } - - /// Return a list of 'key name' and 'program name' pairs for the given keymap. - pub fn hints(&self, available_keys: &AvailableKeys) -> Vec<(String, String)> { - let keys_and_names: Vec<(_, _)> = match available_keys { - AvailableKeys::Mode { - filtered_keys, - name, - } => filtered_keys - .iter() - .map(|&key| (key, self.modes.get(name).unwrap().get(key).unwrap().name())) - .collect(), - AvailableKeys::Menu { - filtered_keys, - name, - } => filtered_keys - .iter() - .map(|&key| (key, self.menus.get(name).unwrap().get(key).unwrap().name())) - .collect(), - AvailableKeys::Text => self.text_keymap.keys_and_names(), - }; - - let mut hints: Vec<_> = keys_and_names - .into_iter() - .map(|(key, name)| (format!("{}", key), name.unwrap_or("...").to_owned())) - .collect(); - hints.sort_unstable(); - hints - } - - /// Return the set of keybindings that should be used to lookup keypresses, - /// based on the current state of the KeymapManager and the context within a - /// particular document. - /// - /// If the document is in text-mode, `tree_context` should be None. - pub fn get_available_keys( - &self, - tree_context: Option, - ) -> Result { - if let Some(context) = tree_context { - if let Some(menu_name) = &self.active_menu { - let menu = self.menus.get(menu_name).unwrap(); - Ok(menu.filter(&context)) - } else { - let mode_name = self.mode_stack.last().ok_or(ServerError::NoMode)?; - let mode = self.modes.get(mode_name).unwrap(); - Ok(mode.filter(&context)) - } - } else { - Ok(AvailableKeys::Text) - } - } -} diff --git a/demo/src/keymaps/mod.rs b/demo/src/keymaps/mod.rs deleted file mode 100644 index 65e8313..0000000 --- a/demo/src/keymaps/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod keymap; -mod keymap_manager; -mod mode_and_menu; - -pub use keymap::{FilterContext, FilterRule, TextKeymap, TreeKeymap}; -pub use keymap_manager::KeymapManager; -pub use mode_and_menu::{AvailableKeys, MenuName, ModeName}; diff --git a/demo/src/keymaps/mode_and_menu.rs b/demo/src/keymaps/mode_and_menu.rs deleted file mode 100644 index 872e954..0000000 --- a/demo/src/keymaps/mode_and_menu.rs +++ /dev/null @@ -1,140 +0,0 @@ -use super::keymap::{FilterContext, TreeKeymap}; -use crate::prog::Prog; -use frontends::Key; - -/// A persistent Mode that specifies which keybindings will be available in which contexts. -/// Only applies when the `Doc` is in tree-mode, not text-mode. (Sorry that -/// there are two different things here called 'modes'.) -/// -/// Intended for things like a mode for a specific programming language that -/// includes some convenient language-specific refactoring commands. -pub(super) struct Mode<'l> { - pub(super) keymap: TreeKeymap<'l>, - pub(super) name: ModeName, -} - -/// A temporary Menu that specifies which keybindings will be available in which contexts. -/// Only applies when the `Doc` is in tree-mode, not text-mode. -/// -/// Intended for things like selecting a node-type from a menu. -pub(super) struct Menu<'l> { - pub(super) keymap: TreeKeymap<'l>, - pub(super) name: MenuName, -} - -/// The name of a `Mode`. -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct ModeName(String); - -/// The name of a `Menu`. -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct MenuName(String); - -/// A description of which keybindings are available to use, based on the [KeymapManager](crate::keymaps::KeymapManager) state and document context. -#[derive(Clone)] -pub enum AvailableKeys { - /// Use this mode for looking up keys. - Mode { - name: ModeName, - /// Which subset of the mode's keys are available. - filtered_keys: Vec, - }, - /// Use this menu for looking up keys. - Menu { - name: MenuName, - /// Which subset of the menu's keys are available. - filtered_keys: Vec, - }, - /// Use the one-and-only text keymap for looking up keys. - Text, -} - -impl<'l> Mode<'l> { - /// Get the program bound to the given key, if there is one. - pub(super) fn get<'a>(&'a self, key: Key) -> Option<&'a Prog<'l>> { - self.keymap.get(key) - } - - /// Produce a filtered keymap containing only the keys that would be - /// appropriate to use in this context. - pub(super) fn filter(&self, context: &FilterContext) -> AvailableKeys { - AvailableKeys::Mode { - filtered_keys: self.keymap.filter(context), - name: self.name.clone(), - } - } -} - -impl<'l> Menu<'l> { - /// Get the program bound to the given key, if there is one. - pub(super) fn get<'a>(&'a self, key: Key) -> Option<&'a Prog<'l>> { - self.keymap.get(key) - } - - /// Produce a filtered keymap containing only the keys that would be - /// appropriate to use in this context. - pub(super) fn filter(&self, context: &FilterContext) -> AvailableKeys { - AvailableKeys::Menu { - filtered_keys: self.keymap.filter(context), - name: self.name.clone(), - } - } -} - -impl AvailableKeys { - pub fn name(&self) -> String { - match self { - AvailableKeys::Menu { name, .. } => name.into(), - AvailableKeys::Mode { name, .. } => name.into(), - AvailableKeys::Text => "text".into(), - } - } -} - -impl From for ModeName { - fn from(s: String) -> ModeName { - ModeName(s) - } -} - -impl<'a> From<&'a str> for ModeName { - fn from(s: &'a str) -> ModeName { - ModeName(s.to_string()) - } -} - -impl From for String { - fn from(m: ModeName) -> String { - m.0 - } -} - -impl<'a> From<&'a ModeName> for String { - fn from(m: &'a ModeName) -> String { - m.0.to_owned() - } -} - -impl From for MenuName { - fn from(s: String) -> MenuName { - MenuName(s) - } -} - -impl<'a> From<&'a str> for MenuName { - fn from(s: &'a str) -> MenuName { - MenuName(s.to_string()) - } -} - -impl From for String { - fn from(m: MenuName) -> String { - m.0 - } -} - -impl<'a> From<&'a MenuName> for String { - fn from(m: &'a MenuName) -> String { - m.0.to_owned() - } -} diff --git a/demo/src/main.rs b/demo/src/main.rs deleted file mode 100644 index 072057e..0000000 --- a/demo/src/main.rs +++ /dev/null @@ -1,27 +0,0 @@ -use editor::NotationSet; - -mod data; -mod engine; -mod error; -mod keymaps; -mod prog; -mod server; - -use editor::NotationSets; -use language::LanguageSet; -use server::Server; - -fn main() { - let language_set = LanguageSet::new(); - let notation_sets = NotationSets::new(); - match Server::new(&language_set, ¬ation_sets) { - Ok(mut ed) => { - let result = ed.run(); - drop(ed); - if let Err(err) = result { - println!("Error: {}", err); - } - } - Err(err) => println!("Failed to create editor: {}", err), - }; -} diff --git a/demo/src/prog.rs b/demo/src/prog.rs deleted file mode 100644 index 506a0ed..0000000 --- a/demo/src/prog.rs +++ /dev/null @@ -1,291 +0,0 @@ -use editor::Ast; - -use language::{ConstructName, LanguageName}; - -use crate::error::ServerError; -use crate::keymaps::{MenuName, ModeName}; - -#[derive(Clone, Debug)] -pub struct Prog<'l> { - /// Optional display name for this program. - pub name: Option, - /// A stack of words to execute, starting at the highest index. - words: Vec>, -} - -pub struct DataStack<'l>(Vec>); -pub struct CallStack<'l>(Vec>); - -#[derive(Clone, Debug)] -pub enum Value<'l> { - Tree(Ast<'l>), - Usize(usize), - Char(char), - ModeName(ModeName), - MenuName(MenuName), - LangConstruct(LanguageName, ConstructName), - String(String), - Quote(Prog<'l>), -} - -#[allow(dead_code)] -#[derive(Clone, Debug)] -pub enum Word<'l> { - // data-stack manipulation: - Swap, - Apply, - Pop, - Literal(Value<'l>), - - // editor-specific: - PushMode, - PopMode, - ActivateMenu, - NodeByName, - Print, - - // tree commands: - InsertHoleAfter, - InsertHoleBefore, - InsertHolePrepend, - InsertHolePostpend, - Replace, - Remove, - Clear, - Left, - Right, - Parent, - Child, - Cut, - Copy, - PasteSwap, - PopClipboard, - Undo, - Redo, - GotoBookmark, - SetBookmark, - - // text commands: - InsertChar, - TreeMode, - DeleteCharBackward, - DeleteCharForward, - TextLeft, - TextRight, -} - -impl<'l> Prog<'l> { - /// Construct a program in which the given words will be executed in order, - /// starting from index 0 of the slice. - pub fn new(forward_words: Vec>) -> Self { - Prog { - words: forward_words.into_iter().rev().collect(), - name: None, - } - } - - /// Construct a program containing one word. Use the word's debug - /// representation as the program name. - pub fn new_single(word: Word<'l>) -> Self { - Prog { - name: Some(format!("{:?}", &word)), - words: vec![word], - } - } - - /// Set the optional program name. - pub fn with_name(self, name: T) -> Self - where - T: ToString, - { - Prog { - name: Some(name.to_string()), - ..self - } - } - - /// Pop the next word from the program. - pub fn pop(&mut self) -> Option> { - self.words.pop() - } - - /// True if there are no words in the program - pub fn is_empty(&self) -> bool { - self.words.is_empty() - } - - /// The display name of the program, if it has one. - pub fn name(&self) -> Option<&str> { - self.name.as_deref() - } - - /// Produce a literal value containing this program. - pub fn quote(self) -> Value<'l> { - Value::Quote(self) - } -} - -impl<'l> From> for Prog<'l> { - /// Unlike `Prog::single()`, this does not give the program a name. - fn from(word: Word<'l>) -> Prog<'l> { - Prog { - name: None, - words: vec![word], - } - } -} - -impl<'l> CallStack<'l> { - /// Construct a new empty callstack. - pub fn new() -> Self { - Self(Vec::new()) - } - - /// Push a program onto the call stack. - pub fn push(&mut self, prog: Prog<'l>) { - self.0.push(prog) - } - - /// Return the next word to execute, removing it from the call stack. Or - /// return None if the call stack is empty. - pub fn next(&mut self) -> Option> { - loop { - let prog = self.0.last_mut()?; - let word = prog.pop(); - if word.is_some() { - if prog.is_empty() { - // Do a sort of tail-call optimization, immediately removing - // the empty program from the stack. - self.0.pop().expect("call stack shouldn't have been empty"); - } - return word; // Success! - } - // Remove empty program and try again - self.0.pop().expect("call stack shouldn't have been empty"); - } - } -} - -impl<'l> DataStack<'l> { - /// Construct a new empty data stack. - pub fn new() -> Self { - Self(Vec::new()) - } - - /// Push this value onto the data stack. - pub fn push(&mut self, value: Value<'l>) { - self.0.push(value); - } - - /// Pop a value from the data stack, returning it. If the stack is empty, - /// return an error. - pub fn pop(&mut self) -> Result, ServerError> { - self.0.pop().ok_or(ServerError::EmptyDataStack) - } - - /// Swap the order of the two top-most values on the data stack. Return an - /// error if there are less than 2 values on the data stack. - pub fn swap(&mut self) -> Result<(), ServerError> { - let first = self.pop()?; - let maybe_second = self.pop(); - self.push(first); - self.push(maybe_second?); - Ok(()) - } - - /// If there is a `Value::Tree` on top of the stack, pop it and return it. - /// Otherwise return an error. - pub fn pop_tree(&mut self) -> Result, ServerError> { - match self.pop()? { - Value::Tree(tree) => Ok(tree), - other => { - self.push(other); - Err(ServerError::ExpectedValue("Tree".into())) - } - } - } - - /// If there is a `Value::Usize` on top of the stack, pop it and return it. - /// Otherwise return an error. - pub fn pop_usize(&mut self) -> Result { - match self.pop()? { - Value::Usize(num) => Ok(num), - other => { - self.push(other); - Err(ServerError::ExpectedValue("Usize".into())) - } - } - } - - /// If there is a `Value::ModeName` on top of the stack, pop it and return it. - /// Otherwise return an error. - pub fn pop_mode_name(&mut self) -> Result { - match self.pop()? { - Value::ModeName(s) => Ok(s), - other => { - self.push(other); - Err(ServerError::ExpectedValue("ModeName".into())) - } - } - } - - /// If there is a `Value::MenuName` on top of the stack, pop it and return - /// it. Otherwise return an error. - pub fn pop_menu_name(&mut self) -> Result { - match self.pop()? { - Value::MenuName(s) => Ok(s), - other => { - self.push(other); - Err(ServerError::ExpectedValue("MenuName".into())) - } - } - } - - /// If there is a `Value::LangConstruct` on top of the stack, pop it and - /// return it. Otherwise return an error. - pub fn pop_lang_construct(&mut self) -> Result<(LanguageName, ConstructName), ServerError> { - match self.pop()? { - Value::LangConstruct(lang_name, construct_name) => Ok((lang_name, construct_name)), - other => { - self.push(other); - Err(ServerError::ExpectedValue("LangConstruct".into())) - } - } - } - - /// If there is a `Value::String` on top of the stack, pop it and return it. - /// Otherwise return an error. - pub fn pop_string(&mut self) -> Result { - match self.pop()? { - Value::String(s) => Ok(s), - other => { - self.push(other); - Err(ServerError::ExpectedValue("String".into())) - } - } - } - - /// If there is a `Value::Char` on top of the stack, pop it and return it. - /// Otherwise return an error. - pub fn pop_char(&mut self) -> Result { - match self.pop()? { - Value::Char(ch) => Ok(ch), - other => { - self.push(other); - Err(ServerError::ExpectedValue("Char".into())) - } - } - } - - /// If there is a `Value::Quote` on top of the stack, pop it and return it. - /// Otherwise return an error. - pub fn pop_quote(&mut self) -> Result, ServerError> { - match self.pop()? { - Value::Quote(prog) => Ok(prog), - other => { - self.push(other); - Err(ServerError::ExpectedValue("Quote".into())) - } - } - } -} diff --git a/demo/src/server.rs b/demo/src/server.rs deleted file mode 100644 index 3e3a1d1..0000000 --- a/demo/src/server.rs +++ /dev/null @@ -1,279 +0,0 @@ -use editor::{ - make_json_lang, Doc, EditorCmd, MetaCommand, NotationSets, TextCmd, TextNavCmd, TreeCmd, - TreeNavCmd, -}; -use frontends::{Event, Frontend, Key, Terminal}; -use language::LanguageSet; -use pretty::ColorTheme; - -use crate::engine::{DocLabel, Engine}; -use crate::error::ServerError; -use crate::keymaps::{AvailableKeys, FilterContext, KeymapManager}; -use crate::prog::{CallStack, DataStack, Value, Word}; - -use crate::data::example_keymaps; -use crate::data::example_pane_notation::make_example_pane_notation; -use crate::data::keyhint_lang::make_keyhint_lang; -use crate::data::message_lang::make_message_lang; - -/// Demonstrate a basic interactive tree editor -pub struct Server<'l> { - engine: Engine<'l>, - frontend: Terminal, - data_stack: DataStack<'l>, - call_stack: CallStack<'l>, - keymap_manager: KeymapManager<'l>, -} - -impl<'l> Server<'l> { - pub fn new( - language_set: &'l LanguageSet, - notation_sets: &'l NotationSets, - ) -> Result { - let engine = Engine::new( - language_set, - notation_sets, - make_example_pane_notation(), - make_keyhint_lang(), - make_message_lang(), - make_json_lang(), - )?; - let mut keymap_manager = KeymapManager::new(); - keymap_manager.register_mode("tree".into(), example_keymaps::make_tree_map()); - keymap_manager.register_mode("speed_bool".into(), example_keymaps::make_speed_bool_map()); - keymap_manager.register_menu( - "node".into(), - example_keymaps::make_node_map( - engine.language(engine.lang_name_of(&DocLabel::ActiveDoc)?)?, - ), - ); - keymap_manager.replace_text_keymap(example_keymaps::make_text_map()); - - let mut ed = Server { - engine, - frontend: Terminal::new(ColorTheme::default_dark())?, - data_stack: DataStack::new(), - call_stack: CallStack::new(), - keymap_manager, - }; - - // Set initial keymap - ed.call(Word::Literal(Value::ModeName("tree".into())))?; - ed.call(Word::PushMode)?; - - // ed.engine.clear_messages()?; - Ok(ed) - } - - /// Run the main loop of the application. - pub fn run(&mut self) -> Result<(), ServerError> { - // In every iteration, we either: - // a) execute a word on the call stack, or - // b) wait for user input that will tell us what new program to push - // onto the call stack. - // - // There are two situations in which we (b) wait for user input. The first - // is if the call stack is empty, leaving us with nothing to do but - // wait. The second is if a program has explicitly requested user input - // by activating a menu. - loop { - if self.keymap_manager.has_active_menu() { - self.handle_input()?; - } else if let Some(word) = self.call_stack.next() { - if let Err(err) = self.call(word) { - self.engine.show_message(&format!("Error: {}", err))?; - } - } else { - // We generally want consecutive text-mode actions to be - // undone together, so hitting `undo` in tree-mode won't - // drop you back into text-mode. Therefore, we should avoid - // ending an undo-group while the the document is in - // text-mode. - if self.engine.active_doc()?.in_tree_mode() { - self.engine.exec(MetaCommand::EndGroup)?; - } - self.handle_input()?; - } - } - } - - fn handle_input(&mut self) -> Result<(), ServerError> { - let available_keys = self - .keymap_manager - .get_available_keys(get_tree_context(self.engine.active_doc()?))?; - - self.update_key_hints(&available_keys)?; - self.engine.redisplay(&mut self.frontend)?; - - match self.frontend.next_event() { - Some(Ok(Event::KeyEvent(Key::Ctrl('c')))) => return Err(ServerError::KeyboardInterrupt), - Some(Ok(Event::KeyEvent(key))) => Ok(key), - Some(Err(err)) => Err(err.into()), - _ => Err(ServerError::UnknownEvent), - } - .and_then(|key| { - self.keymap_manager - .lookup(key, &available_keys) - .ok_or_else(|| ServerError::UnknownKey(key)) - }) - .map(|prog| { - self.call_stack.push(prog); - self.keymap_manager.deactivate_menu(); - }) - .or_else(|err| Ok(self.engine.show_message(&format!("Error: {}", err))?)) - } - - fn update_key_hints(&mut self, available_keys: &AvailableKeys) -> Result<(), ServerError> { - let lang_name = self.engine.lang_name_of(&DocLabel::KeyHints)?; - - let mut keymap_node = self.engine.new_node(&"keymap".into(), lang_name)?; - - for (key, prog) in self.keymap_manager.hints(available_keys) { - let mut key_node = self.engine.new_node(&"key".into(), lang_name)?; - key_node.inner().unwrap_text().text_mut(|t| { - t.activate(); - t.set(key); - t.deactivate(); - }); - - let mut prog_node = self.engine.new_node(&"prog".into(), lang_name)?; - prog_node.inner().unwrap_text().text_mut(|t| { - t.activate(); - t.set(prog); - t.deactivate(); - }); - - let mut binding_node = self.engine.new_node(&"binding".into(), &lang_name)?; - binding_node - .inner() - .unwrap_fixed() - .replace_child(0, key_node) - .unwrap(); - binding_node - .inner() - .unwrap_fixed() - .replace_child(1, prog_node) - .unwrap(); - let mut inner_keymap = keymap_node.inner().unwrap_flexible(); - inner_keymap - .insert_child(inner_keymap.num_children(), binding_node) - .unwrap(); - } - self.engine - .exec_on(TreeCmd::Replace(keymap_node), &DocLabel::KeyHints)?; - - let mut description_node = self - .engine - .new_node_in_doc_lang(&"message".into(), &DocLabel::KeymapName)?; - description_node.inner().unwrap_text().text_mut(|t| { - t.activate(); - t.set(available_keys.name()); - t.deactivate(); - }); - self.engine - .exec_on(TreeCmd::Replace(description_node), &DocLabel::KeymapName)?; - Ok(()) - } - - fn call(&mut self, word: Word<'l>) -> Result<(), ServerError> { - match word { - Word::Literal(value) => self.data_stack.push(value), - Word::Apply => { - let prog = self.data_stack.pop_quote()?; - self.call_stack.push(prog); - } - Word::Swap => { - self.data_stack.swap()?; - } - Word::Pop => { - self.data_stack.pop()?; - } - Word::Print => { - let message = self.data_stack.pop_string()?; - self.engine.show_message(&message)?; - } - Word::NodeByName => { - let (lang_name, construct_name) = self.data_stack.pop_lang_construct()?; - let node = self.engine.new_node(&construct_name, &lang_name)?; - self.data_stack.push(Value::Tree(node)); - } - Word::PushMode => { - let name = self.data_stack.pop_mode_name()?; - self.keymap_manager.push_mode(name)?; - } - Word::PopMode => { - self.keymap_manager.pop_mode(); - } - Word::ActivateMenu => { - let name = self.data_stack.pop_menu_name()?; - if self.keymap_manager.has_active_menu() { - // TODO decide how to handle this - panic!("Another menu is already active"); - } - self.keymap_manager.activate_menu(name)?; - } - Word::Remove => self.engine.exec(TreeCmd::Remove)?, - Word::Clear => self.engine.exec(TreeCmd::Clear)?, - Word::InsertHoleAfter => { - self.engine.exec(TreeCmd::InsertHoleAfter)?; - } - Word::InsertHoleBefore => { - self.engine.exec(TreeCmd::InsertHoleBefore)?; - } - Word::InsertHolePrepend => { - self.engine.exec(TreeCmd::InsertHolePrepend)?; - } - Word::InsertHolePostpend => { - self.engine.exec(TreeCmd::InsertHolePostpend)?; - } - Word::Replace => { - let tree = self.data_stack.pop_tree()?; - self.engine.exec(TreeCmd::Replace(tree))?; - } - Word::Left => self.engine.exec(TreeNavCmd::Left)?, - Word::Right => self.engine.exec(TreeNavCmd::Right)?, - Word::Parent => self.engine.exec(TreeNavCmd::Parent)?, - Word::Child => { - let index = self.data_stack.pop_usize()?; - self.engine.exec(TreeNavCmd::Child(index))?; - } - Word::Undo => self.engine.exec(MetaCommand::Undo)?, - Word::Redo => self.engine.exec(MetaCommand::Redo)?, - Word::Cut => self.engine.exec(EditorCmd::Cut)?, - Word::Copy => self.engine.exec(EditorCmd::Copy)?, - Word::PasteSwap => self.engine.exec(EditorCmd::PasteSwap)?, - Word::PopClipboard => self.engine.exec(EditorCmd::PopClipboard)?, - Word::GotoBookmark => { - let name = self.data_stack.pop_char()?; - let mark = self.engine.get_bookmark(name)?; - self.engine.exec(TreeNavCmd::GotoBookmark(mark))?; - } - Word::SetBookmark => { - let name = self.data_stack.pop_char()?; - self.engine.add_bookmark(name, &DocLabel::ActiveDoc)?; - } - Word::InsertChar => { - let ch = self.data_stack.pop_char()?; - self.engine.exec(TextCmd::InsertChar(ch))?; - } - Word::DeleteCharBackward => self.engine.exec(TextCmd::DeleteCharBackward)?, - Word::DeleteCharForward => self.engine.exec(TextCmd::DeleteCharForward)?, - Word::TreeMode => self.engine.exec(TextNavCmd::TreeMode)?, - Word::TextLeft => self.engine.exec(TextNavCmd::Left)?, - Word::TextRight => self.engine.exec(TextNavCmd::Right)?, - }; - Ok(()) - } -} - -fn get_tree_context<'l>(doc: &Doc<'l>) -> Option { - if doc.in_tree_mode() { - Some(FilterContext { - sort: doc.self_sort(), - self_arity: doc.self_arity_type(), - parent_arity: doc.parent_arity_type(), - }) - } else { - None - } -} diff --git a/demo/src/test_multilang_doc.rs b/demo/src/test_multilang_doc.rs deleted file mode 100644 index c9ff4eb..0000000 --- a/demo/src/test_multilang_doc.rs +++ /dev/null @@ -1,104 +0,0 @@ -use editor::{ - make_json_lang, make_keymap_lang, AstForest, Command, CommandGroup, Doc, TreeCmd, TreeNavCmd, -}; -use language::LanguageSet; -// use pretty::{PlainText, PrettyDocument}; - -#[test] -fn test_keymap_lang() { - let lang_set = LanguageSet::new(); - - let (km_lang, km_note_set) = make_keymap_lang(); - lang_set.insert(km_lang.name().to_owned(), km_lang); - let forest = AstForest::new(&lang_set); - let km = lang_set.get("keymap").unwrap(); - // let km = &km_lang; - - let mut doc2 = Doc::new( - "KeymapDoc", - forest.new_fixed_tree(km, km.lookup_construct("root"), &km_note_set), - ); - - assert!(doc2.execute(CommandGroup::Group(vec![Command::TreeNav( - TreeNavCmd::Child(0) - ),]))); - - assert!( - doc2.execute(CommandGroup::Group(vec![Command::Tree(TreeCmd::Replace( - // forest.new_flexible_tree(&km, km.lookup_construct("dict"), &km_note_set,) - // forest.new_fixed_tree(&km, km.lookup_construct("entry"), &km_note_set,) - forest.new_fixed_tree(&km, km.lookup_construct("entry"), &km_note_set) - )),])) - ); - - // assert!(doc2.execute(CommandGroup::Group(vec![Command::Tree( - // TreeCmd::InsertPrepend(forest.new_flexible_tree( - // &km, - // km.lookup_construct("dict"), - // &km_note_set, - // )) - // ),]))); - - // assert!(doc2.execute(CommandGroup::Group(vec![Command::Tree( - // TreeCmd::InsertPrepend(forest.new_fixed_tree( - // &km, - // km.lookup_construct("entry"), - // &km_note_set, - // )) - // ),]))); -} - -// #[test] -// fn test_json_and_keymap() { -// let (lang_set, json_note_set) = make_json_lang(); -// let (km_lang, km_note_set) = make_keymap_lang(); -// lang_set.insert("keymap".into(), km_lang); - -// let forest = AstForest::new(&lang_set); -// let json = lang_set.get("json").unwrap(); -// let km = lang_set.get("keymap").unwrap(); - -// // let mut doc1 = Doc::new( -// // "JsonDoc", -// // forest.new_fixed_tree(json, json.lookup_construct("root"), &json_note_set), -// // ); - -// // assert!(doc1.execute(CommandGroup::Group(vec![Command::TreeNav( -// // TreeNavCmd::Child(0) -// // ),]))); - -// // assert!(doc1.execute(CommandGroup::Group(vec![ -// // Command::Tree(TreeCmd::Replace(forest.new_flexible_tree( -// // &json, -// // json.lookup_construct("list"), -// // &json_note_set, -// // ))), -// // Command::Tree(TreeCmd::InsertPrepend(forest.new_fixed_tree( -// // &json, -// // json.lookup_construct("true"), -// // &json_note_set, -// // ))), -// // ]))); - -// let mut doc2 = Doc::new( -// "KeymapDoc", -// forest.new_fixed_tree(km, km.lookup_construct("root"), &km_note_set), -// ); - -// assert!(doc2.execute(CommandGroup::Group(vec![Command::TreeNav( -// TreeNavCmd::Child(0) -// ),]))); - -// assert!(doc2.execute(CommandGroup::Group(vec![ -// Command::Tree(TreeCmd::Replace(forest.new_flexible_tree( -// &km, -// km.lookup_construct("dict"), -// &json_note_set, -// ))), -// Command::Tree(TreeCmd::InsertPrepend(forest.new_fixed_tree( -// &km, -// km.lookup_construct("entry"), -// &json_note_set, -// ))), -// ]))); -// } diff --git a/editor/Cargo.toml b/editor/Cargo.toml deleted file mode 100644 index d39f05d..0000000 --- a/editor/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "editor" -version = "0.1.0" -authors = ["Justin "] -edition = "2018" - -[dependencies] -forest = { path = "../forest" } -language = { path = "../language" } - -lazy_static = "*" -thiserror = "1.0" - -[dev-dependencies] -criterion = "0.3" - -[lib] -bench = false - -[[bench]] -name = "render_benchmark" -harness = false diff --git a/editor/benches/render_benchmark.rs b/editor/benches/render_benchmark.rs deleted file mode 100644 index 130dfcf..0000000 --- a/editor/benches/render_benchmark.rs +++ /dev/null @@ -1,59 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use std::time::Duration; - -use editor::{make_json_lang, make_singleton_lang_set, AstRef, TestEditor, TreeCmd, TreeNavCmd}; -use pretty::{ - CursorVisibility, PlainText, PrettyDocument, PrettyWindow, RenderOptions, ScrollStrategy, - WidthStrategy, -}; - -pub fn make_long_list(length: usize, ed: &mut TestEditor) { - ed.exec(TreeNavCmd::Child(0)).unwrap(); - - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - - for _ in 0..(length - 1) { - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - } -} - -pub fn render(ast_ref: AstRef) { - let mut window = PlainText::new_infinite_scroll(80); - let options = RenderOptions { - scroll_strategy: ScrollStrategy::Beginning, - cursor_visibility: CursorVisibility::Hide, - width_strategy: WidthStrategy::Fixed(window.pane().unwrap().rect().width()), - }; - - ast_ref - .pretty_print(&mut window.pane().unwrap(), options) - .unwrap() -} - -pub fn pretty_print(c: &mut Criterion) { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - - let mut group = c.benchmark_group("render_lists"); - group.sample_size(10); - group.measurement_time(Duration::from_secs(20)); - - for length in &[4, 6, 8, 10, 12, 14, 16] { - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name.clone()); - make_long_list(*length, &mut ed); - group.bench_with_input(BenchmarkId::new("list_length", *length), length, |b, _i| { - b.iter(|| render(ed.doc.ast_ref())) - }); - } - - group.finish() -} - -criterion_group!(benches, pretty_print); -criterion_main!(benches); diff --git a/editor/src/ast/ast.rs b/editor/src/ast/ast.rs deleted file mode 100644 index 22acd75..0000000 --- a/editor/src/ast/ast.rs +++ /dev/null @@ -1,441 +0,0 @@ -use std::fmt; - -use crate::notationset::NotationSet; -use crate::text::Text; -use forest::{Bookmark, Tree}; -use language::{Arity, Construct, Language}; -use pretty::{Bounds, Notation}; - -#[derive(Clone)] -pub struct Node<'l> { - pub(super) bounds: Bounds, - pub(super) language: &'l Language, - pub(super) construct: &'l Construct, - pub(super) notation: &'l Notation, -} - -/// An Abstract Syntax Tree. -/// -/// More specifically, this is a mutable reference _to a node_ in an AST. -/// -/// This value owns the entire tree. When it is dropped, the tree is deleted. -/// -/// It also grants write access to the tree. Use [`borrow`](#method.borrow) to -/// obtain a shared reference with read-only access. -/// -/// All write operations mutably borrow the _entire forest_. While an AST is -/// being mutated, or when some of its data is mutably borrowed, _no other tree -/// in the forest can be accessed_. -/// -/// The AST automatically keeps track the `Bounds` information that's needed for -/// pretty-printing. -// TODO: can its data ever be mutably bororwed? If so, give an example. If not, -// delete that phrase. -// -// Many methods here panics if called on a text leaf. Make sure this can't happen. -#[derive(Clone)] -pub struct Ast<'l> { - pub(super) tree: Tree, Text>, -} - -impl<'l> Ast<'l> { - pub(super) fn new(tree: Tree, Text>) -> Ast<'l> { - let mut ast = Ast { tree }; - ast.update(); - ast - } - - /// Get the arity of this node, or `None` if this is a leaf node. - pub fn arity(&self) -> Option { - if self.tree.is_leaf() { - return None; - } - Some(self.tree.data().construct.arity.clone()) - } - - /// Call the closure, giving it read-access to this node's text. - /// - /// # Panics - /// - /// Panics if the arity of this node is not `Text`. - fn text(&self, f: F) -> T - where - F: FnOnce(&Text) -> T, - { - assert_eq!(self.arity(), Some(Arity::Text)); - self.tree.child_leaf(f) - } - - /// Call the closure, giving it write-access to this node's text. - /// - /// # Panics - /// - /// Panics if the arity of this node is not `Text`. - fn text_mut(&mut self, f: F) -> T - where - F: FnOnce(&mut Text) -> T, - { - assert_eq!(self.arity(), Some(Arity::Text)); - let out = self.tree.child_leaf_mut(f); - self.update(); - out - } - - /// Get the language of this node's syntactic construct. - pub fn get_language(&self) -> &'l Language { - &self.tree.data().language - } - - /// Get the syntactic construct this node falls into. - pub fn get_construct(&self) -> &'l Construct { - &self.tree.data().construct - } - - /// Get the notation with which this node is currently displayed. - pub fn get_notation(&self) -> &'l Notation { - &self.tree.data().notation - } - - /// Create a new `hole` node that belongs to the same forest as this node. - pub fn new_hole(&mut self) -> Ast<'l> { - let node = Node { - bounds: Bounds::uninitialized(), - language: self.get_language(), - construct: Construct::hole(), - notation: NotationSet::hole(), - }; - Ast::new(self.tree.forest_mut().new_branch(node, vec![])) - } - - /// Replace this node's `i`th child with the `tree`. Return the replaced - /// child if successful. If `tree` cannot be placed here because it has the wrong - /// Sort, return it as `Err(tree)`. - /// - /// # Panics - /// - /// Panics if this node's arity is not `Fixed` or `Flexible`, or if `i` is - /// out of bounds. - fn replace_child(&mut self, i: usize, tree: Ast<'l>) -> Result, Ast<'l>> { - if let Some(arity) = self.arity() { - if !arity.is_fixed() && !arity.is_flexible() { - panic!("Ast::replace_child called on a node that is neither fixed nor flexible.") - } - if !arity.child_sort(i).accepts(&tree.get_construct().sort) { - // This tree can't go here, it has the wrong Sort! Send it back. - return Err(tree); - } - let ast = Ast { - tree: self.tree.replace_child(i, tree.tree), - }; - self.update(); - Ok(ast) - } else { - panic!("Ast::replace_child called on a leaf node"); - } - } - - /// Insert `tree` as the `i`th child of this node. This node must be - /// `Flexible` or `Mixed`. For nodes of `Mixed` arity, `i` counts both tree - /// and text children. If `tree` cannot be inserted because it has the wrong - /// Sort, return it as `Err(tree)`. - /// - /// # Panics - /// - /// Panics if this node's arity is not `Flexible` or `Mixed`, or if `i` is - /// out of bounds. - fn insert_child(&mut self, i: usize, tree: Ast<'l>) -> Result<(), Ast<'l>> { - if let Some(arity) = self.arity() { - if !arity.is_flexible() && !arity.is_mixed() { - panic!("Ast::insert_child called on a node that isn't Flexible or Mixed") - } - - if !arity.child_sort(i).accepts(&tree.get_construct().sort) { - // This tree can't go here, it has the wrong Sort! Send it back. - return Err(tree); - } - - self.tree.insert_child(i, tree.tree); - self.update(); - Ok(()) - } else { - panic!("Ast::insert_child called on a leaf node"); - } - } - - /// Remove and return the `i`th child of this node. This node must be - /// `Flexible` or `Mixed`. For nodes of `Mixed` arity, `i` counts both tree - /// and text children. - /// - /// # Panics - /// - /// Panics if this node's arity is not `Flexible` or `Mixed`, or if `i` is - /// out of bounds. - fn remove_child(&mut self, i: usize) -> Ast<'l> { - if let Some(arity) = self.arity() { - if !arity.is_flexible() && !arity.is_mixed() { - panic!("Ast::remove_child called on a node that isn't Flexible or Mixed") - } - let ast = Ast { - tree: self.tree.remove_child(i), - }; - self.update(); - ast - } else { - panic!("Ast::remove_child called on leaf node"); - } - } - - /// Determine this node's index among its siblings. Returns `0` when at the - /// root. For Mixed parents, counts both text and tree children. - pub fn index(&self) -> usize { - self.tree.index() - } - - /// Determine the number of siblings that this node has, including itself. - /// For Mixed parents, counts both text and tree children. When at the root, - /// returns 1. - pub fn num_siblings(&self) -> usize { - self.tree.num_siblings() - } - - /// Returns `true` if this is the root of the tree, and `false` if - /// it isn't (and thus this node has a parent). - pub fn is_at_root(&self) -> bool { - self.tree.is_at_root() - } - - /// Return `true` if this node is a child of the root of the tree, and `false` otherwise. - pub fn is_parent_at_root(&self) -> bool { - self.tree.is_parent_at_root() - } - - /// Return the number of children this node has. For a Fixed node, this is - /// its arity. For a Flexible node, this is its current number of children. - /// For a Mixed node, this is its _total number_ of children, counting both - /// tree and text children. - /// - /// # Panics - /// - /// Panics if the arity of this node is `Text`. - fn num_children(&self) -> usize { - match self.arity() { - None => panic!("Ast::num_children called on a leaf node"), - Some(Arity::Text) => panic!("Ast::num_children called on a Text node"), - Some(_) => self.tree.num_children(), - } - } - - /// Go to the parent of this node. Returns this node's index among its - /// siblings (so that you can return to it later). - /// - /// # Panics - /// - /// Panics if this is the root of the tree, and there is no parent. - pub fn goto_parent(&mut self) -> usize { - self.tree.goto_parent() - } - - /// Go to the i'th child of this node's parent. - /// - /// # Panics - /// - /// Panics if the index is out of bounds. - pub fn goto_sibling(&mut self, i: usize) { - self.tree.goto_parent(); - self.tree.goto_child(i); - } - - /// Go to this tree's root. - pub fn goto_root(&mut self) { - self.tree.goto_root() - } - - /// Go to this node's i'th child. For nodes of `Mixed` arity, `i` counts - /// both tree and text children. - /// - /// # Panics - /// - /// Panics if the arity of this node is `Text`, or if `i` is out of bounds. - fn goto_child(&mut self, i: usize) { - self.tree.goto_child(i) - } - - /// Save a bookmark to return to later. - pub fn bookmark(&mut self) -> Bookmark { - self.tree.bookmark() - } - - /// Jump to a previously saved bookmark, as long as that - /// bookmark's node is present somewhere in this tree. This will - /// work even if the Tree has been modified since the bookmark was - /// created. However, it will return `false` if the bookmark's node - /// has since been deleted, or if it is currently located in a - /// different tree. - pub fn goto_bookmark(&mut self, mark: Bookmark) -> bool { - self.tree.goto_bookmark(mark) - } - - // Panics if it's a leaf - pub fn inner<'a>(&'a mut self) -> AstKind<'a, 'l> { - match self.arity().expect("Ast::inner() - at a leaf node") { - Arity::Text => AstKind::Text(TextAst { ast: self }), - Arity::Fixed(_) => AstKind::Fixed(FixedAst { ast: self }), - Arity::Flexible(_) => AstKind::Flexible(FlexibleAst { ast: self }), - Arity::Mixed(_) => unimplemented!(), - } - } - - /// Update bounds. This must be called every time the tree is modified! - /// - /// # Panics - /// - /// Panics if this is a leaf node. - fn update(&mut self) { - let bookmark = self.bookmark(); - self.tree.data_mut().bounds = Bounds::compute(&self.ast_ref()); - while !self.is_at_root() { - self.goto_parent(); - self.tree.data_mut().bounds = Bounds::compute(&self.ast_ref()); - } - self.goto_bookmark(bookmark); - } -} - -impl<'l> fmt::Debug for Ast<'l> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // The contents of Ast are complicated, but it should implement Debug so - // that we can derive Debug for structs containing Asts. - write!(f, "Ast") - } -} - -pub enum AstKind<'a, 'l> { - Text(TextAst<'a, 'l>), - Fixed(FixedAst<'a, 'l>), - Flexible(FlexibleAst<'a, 'l>), -} - -impl<'a, 'l> AstKind<'a, 'l> { - pub fn unwrap_text(self) -> TextAst<'a, 'l> { - match self { - AstKind::Text(ast) => ast, - _ => panic!("expected AstKind::Text"), - } - } - pub fn unwrap_fixed(self) -> FixedAst<'a, 'l> { - match self { - AstKind::Fixed(ast) => ast, - _ => panic!("expected AstKind::Fixed"), - } - } - pub fn unwrap_flexible(self) -> FlexibleAst<'a, 'l> { - match self { - AstKind::Flexible(ast) => ast, - _ => panic!("expected AstKind::Flexible"), - } - } -} - -/// A wrapper around an `Ast` with `Text` arity. -pub struct TextAst<'a, 'l> { - ast: &'a mut Ast<'l>, -} - -impl<'a, 'l> TextAst<'a, 'l> { - /// Call the closure, giving it read-access to this node's text. - pub fn text(&self, f: F) -> T - where - F: FnOnce(&Text) -> T, - { - self.ast.text(f) - } - - /// Call the closure, giving it write-access to this node's text. - pub fn text_mut(&mut self, f: F) -> T - where - F: FnOnce(&mut Text) -> T, - { - self.ast.text_mut(f) - } -} - -/// A wrapper around an `Ast` with `Fixed` arity. -pub struct FixedAst<'a, 'l> { - ast: &'a mut Ast<'l>, -} - -impl<'a, 'l> FixedAst<'a, 'l> { - /// Return the number of children this node has. For a Fixed node, this is - /// the same as its arity. - pub fn num_children(&self) -> usize { - self.ast.num_children() - } - - /// Go to this node's i'th child. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn goto_child(&mut self, i: usize) { - self.ast.goto_child(i) - } - - /// Replace this node's `i`th child. If successful, return the replaced - /// child. Otherwise, return the given tree as `Err(tree)`. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn replace_child(&mut self, i: usize, tree: Ast<'l>) -> Result, Ast<'l>> { - self.ast.replace_child(i, tree) - } -} - -/// A wrapper around an `Ast` with `Flexible` arity. -pub struct FlexibleAst<'a, 'l> { - ast: &'a mut Ast<'l>, -} - -impl<'a, 'l> FlexibleAst<'a, 'l> { - /// Return the number of children this node currently has. - pub fn num_children(&self) -> usize { - self.ast.num_children() - } - - /// Go to this node's i'th child. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn goto_child(&mut self, i: usize) { - self.ast.goto_child(i) - } - - /// Replace this node's `i`th child. If successful, return the replaced - /// child. Otherwise, return the given tree as `Err(tree)`. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn replace_child(&mut self, i: usize, tree: Ast<'l>) -> Result, Ast<'l>> { - self.ast.replace_child(i, tree) - } - - /// Insert `tree` as the `i`th child of this node. If unsuccessful, return the given tree as `Err(tree)`. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn insert_child(&mut self, i: usize, tree: Ast<'l>) -> Result<(), Ast<'l>> { - self.ast.insert_child(i, tree) - } - - /// Remove and return the `i`th child of this node. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn remove_child(&mut self, i: usize) -> Ast<'l> { - self.ast.remove_child(i) - } -} diff --git a/editor/src/ast/ast_forest.rs b/editor/src/ast/ast_forest.rs deleted file mode 100644 index 31b1b1e..0000000 --- a/editor/src/ast/ast_forest.rs +++ /dev/null @@ -1,159 +0,0 @@ -use forest::Forest; -use language::{Arity, Construct, ConstructName, Language, LanguageSet, BUILTIN_CONSTRUCTS}; -use pretty::Bounds; - -use crate::ast::ast::{Ast, Node}; -use crate::notationset::NotationSet; -use crate::text::Text; - -/// All [Asts](Ast) belong to an AstForest. -/// -/// It is your responsibility to ensure that Asts are kept with the -/// forest they came from. The methods on Asts will panic if you use -/// them on a different forest. -pub struct AstForest<'l> { - language_set: &'l LanguageSet, - forest: Forest, Text>, -} - -impl<'l> AstForest<'l> { - /// Construct a new, empty, forest. - pub fn new(language_set: &'l LanguageSet) -> AstForest<'l> { - AstForest { - language_set, - forest: Forest::new(), - } - } - - pub fn new_tree( - &self, - language: &'l Language, - construct_name: &ConstructName, - notation_set: &'l NotationSet, - ) -> Option> { - let construct = language.lookup_construct(construct_name); - let node = Node { - bounds: Bounds::uninitialized(), - language, - construct, - notation: notation_set.lookup(construct_name), - }; - let ast = match &construct.arity { - Arity::Fixed(sorts) => { - let children = vec![self.new_hole_tree(language, notation_set).tree; sorts.len()]; - Ast::new(self.forest.new_branch(node, children)) - } - Arity::Flexible(_) => Ast::new(self.forest.new_branch(node, vec![])), - Arity::Text => { - let leaf = self.forest.new_leaf(Text::new_inactive()); - Ast::new(self.forest.new_branch(node, vec![leaf])) - } - Arity::Mixed(..) => unimplemented!("Mixed-arity trees"), - }; - Some(ast) - } - - /// Construct a hole in this forest, that represents a gap in the document. - pub fn new_hole_tree(&self, language: &'l Language, notation_set: &'l NotationSet) -> Ast<'l> { - let hole = BUILTIN_CONSTRUCTS - .get(&"hole".into()) - .expect("no builtin 'hole' construct found"); - let node = Node { - bounds: Bounds::uninitialized(), - language, - construct: hole, - notation: notation_set.lookup(&hole.name), - }; - Ast::new(self.forest.new_branch(node, vec![])) - } - - /// Construct a new tree in this forest, of `Text` arity. - pub fn new_text_tree( - &self, - language: &'l Language, - construct: &'l Construct, - notation_set: &'l NotationSet, - ) -> Ast<'l> { - let node = Node { - bounds: Bounds::uninitialized(), - language, - construct, - notation: notation_set.lookup(&construct.name), - }; - if !construct.arity.is_text() { - panic!( - "AstForest::new_text_tree - expected a node of text arity, but found arity {}", - construct.arity - ) - } - let leaf = self.forest.new_leaf(Text::new_inactive()); - Ast::new(self.forest.new_branch(node, vec![leaf])) - } - - // TODO: check that language has construct! UUID? - /// Construct a new tree in this forest, of `Fixed` arity. - pub fn new_fixed_tree( - &self, - language: &'l Language, - construct: &'l Construct, - notation_set: &'l NotationSet, - ) -> Ast<'l> { - let node = Node { - bounds: Bounds::uninitialized(), - language, - construct, - notation: notation_set.lookup(&construct.name), - }; - let arity = match &construct.arity { - Arity::Fixed(sorts) => sorts.len(), - a => panic!( - "AstForest::new_fixed_tree - expected a node of fixed arity, but found arity {}", - a - ), - }; - let children = vec![self.new_hole_tree(language, notation_set).tree; arity]; - Ast::new(self.forest.new_branch(node, children)) - } - - /// Construct a new tree in this forest, of `Flexible` arity. - pub fn new_flexible_tree( - &self, - language: &'l Language, - construct: &'l Construct, - notation_set: &'l NotationSet, - ) -> Ast<'l> { - let node = Node { - bounds: Bounds::uninitialized(), - language, - construct, - notation: notation_set.lookup(&construct.name), - }; - if !construct.arity.is_flexible() { - panic!("AstForest::new_flexible_tree - expected a node of flexible arity, but found arity {}", construct.arity) - } - Ast::new(self.forest.new_branch(node, vec![])) - } - - /// Construct a new tree in this forest, of `Mixed` arity. - pub fn new_mixed_tree( - &self, - language: &'l Language, - construct: &'l Construct, - notation_set: &'l NotationSet, - ) -> Ast<'l> { - // TODO: probably shouldn't be copy-pasting this - let node = Node { - bounds: Bounds::uninitialized(), - language, - construct, - notation: notation_set.lookup(&construct.name), - }; - if !construct.arity.is_mixed() { - panic!( - "AstForest::new_mixed_tree - expected a node of mixed arity, but found arity {}", - construct.arity - ) - } - Ast::new(self.forest.new_branch(node, vec![])) - } -} diff --git a/editor/src/ast/ast_ref.rs b/editor/src/ast/ast_ref.rs deleted file mode 100644 index 4f56591..0000000 --- a/editor/src/ast/ast_ref.rs +++ /dev/null @@ -1,122 +0,0 @@ -use forest::{Bookmark, TreeRef}; -use language::Arity; -use pretty::{Bounds, Notation, PrettyDocument}; - -use crate::ast::ast::{Ast, Node}; -use crate::text::Text; - -impl<'f, 'l> Ast<'l> { - pub fn ast_ref(&'f self) -> AstRef<'f, 'l> { - AstRef { - tree_ref: self.tree.borrow(), - } - } -} - -/// An immutable reference to a node in an AST. -#[derive(Clone)] -pub struct AstRef<'f, 'l> { - tree_ref: TreeRef<'f, Node<'l>, Text>, -} - -impl<'f, 'l> AstRef<'f, 'l> { - /// Get the parent node and this node's index. Returns `None` if we're - /// already at the root of the tree. - pub fn parent(&self) -> Option<(AstRef<'f, 'l>, usize)> { - match self.tree_ref.parent() { - None => None, - Some(tree_ref) => { - let index = self.tree_ref.index(); - Some((AstRef { tree_ref }, index)) - } - } - } - - /// Get the arity of this node. - pub fn arity(&self) -> Arity { - self.tree_ref.data().construct.arity.clone() - } - - /// Get the children of a Fixed, Flexible, or Mixed node. - /// - /// # Panics - /// - /// Panics if the arity of this node is `Text`. - pub fn children(&self) -> impl Iterator> { - self.tree_ref.children().map(|tr| AstRef { tree_ref: tr }) - } - - /// Get the `i`th child of a Fixed, Flexible, or Mixed node. - /// - /// # Panics - /// - /// Panics if the arity of this node is `Text`. - pub fn child(&self, i: usize) -> AstRef<'f, 'l> { - AstRef { - tree_ref: self.tree_ref.child(i), - } - } - - /// Calls the closure, giving it read-access to this node's text. - /// - /// # Panics - /// - /// Panics if the arity of this node is not `Text`. - pub fn text(&self, f: F) -> T - where - F: FnOnce(&Text) -> T, - { - assert!(self.arity().is_text()); - self.tree_ref.child_leaf(f) - } - - /// Save a bookmark to return to later. - pub fn bookmark(&self) -> Bookmark { - self.tree_ref.bookmark() - } - - /// Return to a previously saved bookmark, as long as that bookmark's node - /// is present somewhere in this tree. This will work even if the Tree has - /// been modified since the bookmark was created. However, this method will - /// return `None` if the bookmark's node has since been deleted, or if it is - /// currently located in a different tree. - pub fn lookup_bookmark(&self, mark: Bookmark) -> Option> { - self.tree_ref - .lookup_bookmark(mark) - .map(|tr| AstRef { tree_ref: tr }) - } -} - -impl<'f, 'l> PrettyDocument for AstRef<'f, 'l> { - type TextRef = String; - - fn notation(&self) -> &Notation { - &self.tree_ref.data().notation - } - - fn bounds(&self) -> Bounds { - self.tree_ref.data().bounds.clone() - } - - fn text(&self) -> Option { - // TODO can we avoid allocating a string every time? - if self.arity() == Arity::Text { - Some(self.text(|t| t.as_str().to_string())) - } else { - None - } - } - - fn parent(&self) -> Option<(AstRef<'f, 'l>, usize)> { - self.parent() - } - - // TODO: probably panics on mixed nodes. - fn child(&self, i: usize) -> AstRef<'f, 'l> { - self.child(i) - } - - fn children(&self) -> Vec> { - self.children().collect() - } -} diff --git a/editor/src/ast/mod.rs b/editor/src/ast/mod.rs deleted file mode 100644 index a683df2..0000000 --- a/editor/src/ast/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -// TODO rename modules to fix this for real -#[allow(clippy::module_inception)] -mod ast; - -mod ast_forest; -mod ast_ref; - -pub use self::ast::{Ast, AstKind}; -pub use self::ast_forest::AstForest; -pub use self::ast_ref::AstRef; diff --git a/editor/src/command.rs b/editor/src/command.rs deleted file mode 100644 index f54cc13..0000000 --- a/editor/src/command.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::ast::Ast; -use forest::Bookmark; - -#[derive(Debug)] -pub enum MetaCommand<'l> { - /// Execute the given command. - Do(Command<'l>), - /// Undo the most recent undo-group. - Undo, - /// Redo the most recently undone undo-group. - Redo, - /// End the current undo-group. Commands executed after this will be placed in a new undo-group. - EndGroup, -} - -#[derive(Debug)] -pub enum Command<'l> { - Ed(EditorCmd), - Tree(TreeCmd<'l>), - TreeNav(TreeNavCmd), - Text(TextCmd), - TextNav(TextNavCmd), -} - -#[derive(Debug)] -pub enum EditorCmd { - /// Cut onto the clipboard. - Cut, - /// Copy onto the clipboard. - Copy, - /// Swap the current node with the node on the top of the clipboard. - PasteSwap, - /// Discard the node on the top of the clipboard. - PopClipboard, -} - -#[derive(Debug)] -pub enum TreeCmd<'l> { - /// Replace the current node with the given node. - Replace(Ast<'l>), - /// In a flexible sequence, insert a hole to the left of the current node. - InsertHoleBefore, - /// In a flexible sequence, insert a hole to the right of the current node. - InsertHoleAfter, - /// On a flexible parent, insert a hole at the beginning of its children. - InsertHolePrepend, - /// On a flexible parent, insert a hole at the end of its children. - InsertHolePostpend, - /// In a flexible sequence, remove the current node. - Remove, - /// Replace the current node with a hole. - Clear, -} - -#[derive(Debug)] -pub enum TreeNavCmd { - /// Move cursor to left sibling - Left, - /// Move cursor to right sibling - Right, - /// Move cursor to first child - Child(usize), - /// Move cursor to parent - Parent, - /// Move to the node pointed to by the Bookmark - GotoBookmark(Bookmark), -} - -#[derive(Debug)] -pub enum TextCmd { - /// Insert the given character at the cursor position - InsertChar(char), - /// Delete the character immediately before the cursor - DeleteCharBackward, - /// Delete the character immediately after the cursor - DeleteCharForward, -} - -#[derive(Debug)] -pub enum TextNavCmd { - /// Move cursor left one character - Left, - /// Move cursor right one character - Right, - /// Return to tree mode - TreeMode, -} - -impl<'l> From> for Command<'l> { - fn from(cmd: TreeCmd<'l>) -> Command<'l> { - Command::Tree(cmd) - } -} - -impl<'l> From for Command<'l> { - fn from(cmd: TreeNavCmd) -> Command<'l> { - Command::TreeNav(cmd) - } -} - -impl<'l> From for Command<'l> { - fn from(cmd: TextCmd) -> Command<'l> { - Command::Text(cmd) - } -} - -impl<'l> From for Command<'l> { - fn from(cmd: TextNavCmd) -> Command<'l> { - Command::TextNav(cmd) - } -} - -impl<'l> From for Command<'l> { - fn from(cmd: EditorCmd) -> Command<'l> { - Command::Ed(cmd) - } -} - -impl<'l, T> From for MetaCommand<'l> -where - T: Into>, -{ - fn from(cmd_like: T) -> MetaCommand<'l> { - MetaCommand::Do(cmd_like.into()) - } -} diff --git a/editor/src/doc.rs b/editor/src/doc.rs deleted file mode 100644 index be01b80..0000000 --- a/editor/src/doc.rs +++ /dev/null @@ -1,624 +0,0 @@ -use std::{iter, mem, vec}; - -use crate::ast::{Ast, AstKind, AstRef}; -use crate::command::{Command, EditorCmd, MetaCommand, TextCmd, TextNavCmd, TreeCmd, TreeNavCmd}; -use forest::Bookmark; -use language::{ArityType, Sort}; - -#[derive(thiserror::Error, Debug)] -pub enum DocError { - #[error("cannot execute text command while not in text mode")] - NotInTextMode, - #[error("cannot execute tree command while not in tree mode")] - NotInTreeMode, - #[error("nothing to undo")] - NothingToUndo, - #[error("nothing to redo")] - NothingToRedo, - #[error("cannot use node because it's of the wrong sort")] - WrongSort, - #[error("cannot paste that here")] - CannotPaste, // TODO this is just a special case of WrongSort, combine them? - #[error("clipboard is empty")] - EmptyClipboard, - #[error("cannot move there")] - CannotMove, - #[error("cannot remove this node")] - CannotRemoveNode, - #[error("cannot delete character here")] - CannotDeleteChar, - #[error("cannot insert node here")] - CannotInsert, -} - -#[derive(Debug)] -pub struct UndoGroup<'l> { - contains_edit: bool, - commands: Vec>, -} - -impl<'l> UndoGroup<'l> { - fn new() -> UndoGroup<'l> { - UndoGroup { - contains_edit: false, - commands: vec![], - } - } - - fn with_edit(commands: Vec>) -> Self { - UndoGroup { - contains_edit: true, - commands, - } - } - - fn append(&mut self, mut other: UndoGroup<'l>) { - self.contains_edit |= other.contains_edit; - self.commands.append(&mut other.commands); - } - - fn clear(&mut self) { - self.contains_edit = false; - self.commands = vec![]; - } - - fn is_empty(&self) -> bool { - self.commands.is_empty() - } -} - -impl<'l> IntoIterator for UndoGroup<'l> { - type Item = Command<'l>; - type IntoIter = iter::Rev>>; - - fn into_iter(self) -> Self::IntoIter { - self.commands.into_iter().rev() - } -} - -/// A stack containing Asts that have been cut or copied. -#[derive(Default)] -pub struct Clipboard<'l>(Vec>); - -impl<'l> Clipboard<'l> { - /// Construct a new, empty clipboard stack. - pub fn new() -> Self { - Clipboard(Vec::new()) - } - - /// Push the given tree onto the clipboard stack. - pub fn push(&mut self, new_ast: Ast<'l>) { - self.0.push(new_ast); - } - - /// Pop a tree off the top of the clipboard stack, returning None if the - /// clipboard is empty. - pub fn pop(&mut self) -> Option> { - self.0.pop() - } - - /// Return the number of trees on the clipboard stack. - pub fn len(&mut self) -> usize { - self.0.len() - } - - /// Return true if there are no trees on the clipboard stack. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -#[derive(Clone, Copy)] -pub enum Mode { - Tree, - /// byte-index of text cursor - Text(usize), -} - -impl Mode { - pub fn is_tree_mode(&self) -> bool { - match self { - Mode::Tree => true, - _ => false, - } - } - - fn text_pos(&self) -> Option { - match self { - Mode::Text(pos) => Some(*pos), - _ => None, - } - } -} - -pub struct Doc<'l> { - name: String, - recent: UndoGroup<'l>, - undo_stack: Vec>, - redo_stack: Vec>, - ast: Ast<'l>, - mode: Mode, -} - -impl<'l> Doc<'l> { - pub fn new(name: &str, ast: Ast<'l>) -> Self { - Doc { - name: name.to_owned(), - recent: UndoGroup::new(), - undo_stack: Vec::new(), - redo_stack: Vec::new(), - ast, - mode: Mode::Tree, - } - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn ast_ref<'f>(&'f self) -> AstRef<'f, 'l> { - self.ast.ast_ref() - } - - pub fn in_tree_mode(&self) -> bool { - self.mode.is_tree_mode() - } - - /// Get a Bookmark pointing to the current node. - pub fn bookmark(&mut self) -> Bookmark { - self.ast.bookmark() - } - - /// Get the Sort of the current node. - pub fn self_sort(&self) -> Sort { - let (parent, index) = self - .ast_ref() - .parent() - .expect("you shouldn't be at the root!"); - parent.arity().child_sort(index).to_owned() - } - - /// Get the arity type of the current node. - pub fn self_arity_type(&self) -> ArityType { - self.ast_ref().arity().into() - } - - pub fn parent_arity_type(&self) -> ArityType { - let (parent, _) = self - .ast_ref() - .parent() - .expect("you shouldn't be at the root!"); - parent.arity().into() - } - - fn take_recent(&mut self) -> UndoGroup<'l> { - mem::replace(&mut self.recent, UndoGroup::new()) - } - - fn end_undo_group(&mut self) { - if !self.recent.is_empty() { - let undos = self.take_recent(); - self.undo_stack.push(undos) - } - } - - fn undo(&mut self, clipboard: &mut Clipboard<'l>) -> Result<(), DocError> { - self.end_undo_group(); - - // Find the most recent group that contains an edit - let index = self - .undo_stack - .iter() - .rposition(|group| group.contains_edit); - - if let Some(index) = index { - // Undo everything up to and including that group - for _ in 0..(self.undo_stack.len() - index) { - let group = self - .undo_stack - .pop() - .expect("undo stack shouldn't be empty!"); - for cmd in group { - self.execute_command(cmd, clipboard) - .expect("Failed to undo"); - } - let recent = self.take_recent(); - self.redo_stack.push(recent); - } - Ok(()) - } else { - Err(DocError::NothingToUndo) // There are no edits on the undo stack - } - } - - fn redo(&mut self, clipboard: &mut Clipboard<'l>) -> Result<(), DocError> { - self.end_undo_group(); - - // Find the most recent group that contains an edit - let index = self - .redo_stack - .iter() - .rposition(|group| group.contains_edit); - - if let Some(index) = index { - // Redo everything up to and including that group - for _ in 0..(self.redo_stack.len() - index) { - let group = self - .redo_stack - .pop() - .expect("redo stack shouldn't be empty!"); - for cmd in group { - self.execute_command(cmd, clipboard) - .expect("Failed to redo"); - } - let recent = self.take_recent(); - self.undo_stack.push(recent); - } - Ok(()) - } else { - Err(DocError::NothingToRedo) // There are no edits on the redo stack - } - } - - pub fn execute( - &mut self, - meta_cmd: MetaCommand<'l>, - clipboard: &mut Clipboard<'l>, - ) -> Result<(), DocError> { - match meta_cmd { - MetaCommand::Undo => self.undo(clipboard), - MetaCommand::Redo => self.redo(clipboard), - MetaCommand::EndGroup => { - self.end_undo_group(); - Ok(()) - } - MetaCommand::Do(cmd) => { - self.redo_stack.clear(); - self.execute_command(cmd, clipboard) - } - } - } - - fn execute_command( - &mut self, - cmd: Command<'l>, - clipboard: &mut Clipboard<'l>, - ) -> Result<(), DocError> { - let undos = match cmd { - Command::Ed(cmd) => self.execute_ed(cmd, clipboard)?, - Command::Tree(cmd) => self.execute_tree(cmd)?, - Command::TreeNav(cmd) => self.execute_tree_nav(cmd)?, - Command::Text(cmd) => self.execute_text(cmd)?, - Command::TextNav(cmd) => self.execute_text_nav(cmd)?, - }; - self.recent.append(undos); - Ok(()) - } - - fn execute_ed( - &mut self, - cmd: EditorCmd, - clipboard: &mut Clipboard<'l>, - ) -> Result, DocError> { - if !self.mode.is_tree_mode() { - return Err(DocError::NotInTreeMode); - } - match cmd { - EditorCmd::Cut => { - let hole = self.ast.new_hole(); - let old_ast = self.replace(hole).map_err(|_| DocError::WrongSort)?; - // Put a copy on the clipboard (breaking bookmarks) - clipboard.push(old_ast.clone()); - // Put the original on the undo stack (preserving bookmarks) - let undos = vec![TreeCmd::Replace(old_ast).into()]; - Ok(UndoGroup::with_edit(undos)) - } - EditorCmd::Copy => { - clipboard.push(self.ast.clone()); - Ok(UndoGroup::new()) - } - EditorCmd::PasteSwap => { - let tree = clipboard.pop().ok_or(DocError::EmptyClipboard)?; - let old_ast = self.replace(tree).map_err(|rejected_tree| { - // Can't paste that here, put it back! - clipboard.push(rejected_tree); - DocError::CannotPaste - })?; - // Put a copy on the clipboard (breaking bookmarks) - clipboard.push(old_ast.clone()); - // Put the original on the undo stack (preserving bookmarks) - let undos = vec![TreeCmd::Replace(old_ast).into()]; - Ok(UndoGroup::with_edit(undos)) - } - EditorCmd::PopClipboard => { - if clipboard.pop().is_none() { - Err(DocError::EmptyClipboard) - } else { - Ok(UndoGroup::new()) - } - } - } - } - - fn execute_tree(&mut self, cmd: TreeCmd<'l>) -> Result, DocError> { - if !self.mode.is_tree_mode() { - return Err(DocError::NotInTreeMode); - } - let undos = match cmd { - TreeCmd::Replace(new_ast) => { - let old_ast = self.replace(new_ast).map_err(|_| DocError::WrongSort)?; - vec![TreeCmd::Replace(old_ast).into()] - } - TreeCmd::Remove => self.remove()?, - TreeCmd::InsertHoleBefore => self.insert_sibling(true)?, - TreeCmd::InsertHoleAfter => self.insert_sibling(false)?, - TreeCmd::InsertHolePrepend => self.insert_child_at_edge(true)?, - TreeCmd::InsertHolePostpend => self.insert_child_at_edge(false)?, - TreeCmd::Clear => { - let hole = self.ast.new_hole(); - let old_ast = self.replace(hole).map_err(|_| DocError::WrongSort)?; - vec![TreeCmd::Replace(old_ast).into()] - } - }; - Ok(UndoGroup { - contains_edit: true, - commands: undos, - }) - } - - fn execute_tree_nav(&mut self, cmd: TreeNavCmd) -> Result, DocError> { - if !self.mode.is_tree_mode() { - return Err(DocError::NotInTreeMode); - } - let undos = match cmd { - TreeNavCmd::Left => { - let i = self.ast.index(); - if i == 0 { - return Err(DocError::CannotMove); - } - self.ast.goto_sibling(i - 1); - vec![TreeNavCmd::Right.into()] - } - TreeNavCmd::Right => { - let i = self.ast.index(); - let n = self.ast.num_siblings(); - if i + 1 >= n { - return Err(DocError::CannotMove); - } - self.ast.goto_sibling(i + 1); - vec![TreeNavCmd::Left.into()] - } - TreeNavCmd::Parent => { - if self.ast.is_parent_at_root() { - // User should never be able to select the root node - return Err(DocError::CannotMove); - } - let i = self.ast.goto_parent(); - vec![TreeNavCmd::Child(i).into()] - } - TreeNavCmd::Child(i) => match self.ast.inner() { - AstKind::Text(mut text) => { - // Enter text mode - self.mode = Mode::Text(i); - text.text_mut(|t| t.activate()); - vec![TextNavCmd::TreeMode.into()] - } - AstKind::Fixed(mut ast) => { - if i >= ast.num_children() { - return Err(DocError::CannotMove); - } - ast.goto_child(i); - vec![TreeNavCmd::Parent.into()] - } - AstKind::Flexible(mut ast) => { - if i >= ast.num_children() { - return Err(DocError::CannotMove); - } - ast.goto_child(i); - vec![TreeNavCmd::Parent.into()] - } - }, - TreeNavCmd::GotoBookmark(bookmark) => { - let here = self.ast.bookmark(); - if self.ast.goto_bookmark(bookmark) { - vec![TreeNavCmd::GotoBookmark(here).into()] - } else { - return Err(DocError::CannotMove); - } - } - }; - Ok(UndoGroup { - contains_edit: false, - commands: undos, - }) - } - - fn execute_text(&mut self, cmd: TextCmd) -> Result, DocError> { - let char_index = self.mode.text_pos().ok_or(DocError::NotInTextMode)?; - let mut ast = self.ast.inner().unwrap_text(); - let undos = match cmd { - TextCmd::InsertChar(character) => { - ast.text_mut(|t| t.insert(char_index, character)); - self.mode = Mode::Text(char_index + 1); - vec![TextCmd::DeleteCharBackward.into()] - } - TextCmd::DeleteCharForward => { - let text_len = ast.text(|t| t.num_chars()); - if char_index == text_len { - return Err(DocError::CannotDeleteChar); - } - let c = ast.text_mut(|t| t.delete(char_index)); - vec![TextCmd::InsertChar(c).into()] - } - TextCmd::DeleteCharBackward => { - if char_index == 0 { - return Err(DocError::CannotDeleteChar); - } - let c = ast.text_mut(|t| t.delete(char_index - 1)); - self.mode = Mode::Text(char_index - 1); - vec![TextCmd::InsertChar(c).into()] - } - }; - Ok(UndoGroup { - contains_edit: true, - commands: undos, - }) - } - - fn execute_text_nav(&mut self, cmd: TextNavCmd) -> Result, DocError> { - let char_index = self.mode.text_pos().ok_or(DocError::NotInTextMode)?; - let mut ast = self.ast.inner().unwrap_text(); - let undos = match cmd { - TextNavCmd::Left => { - if char_index == 0 { - return Err(DocError::CannotMove); - } - self.mode = Mode::Text(char_index - 1); - vec![TextNavCmd::Right.into()] - } - TextNavCmd::Right => { - if char_index >= ast.text(|t| t.num_chars()) { - return Err(DocError::CannotMove); - } - self.mode = Mode::Text(char_index + 1); - vec![TextNavCmd::Left.into()] - } - TextNavCmd::TreeMode => { - // Exit text mode - ast.text_mut(|t| t.deactivate()); - self.mode = Mode::Tree; - vec![TreeNavCmd::Child(char_index).into()] - } - }; - Ok(UndoGroup { - contains_edit: false, - commands: undos, - }) - } - - /// If `at_start` is true, insert the new ast as the first child of this - /// node. Otherwise, insert it as the last child. If the insertion is - /// successful, return the list of commands needed to undo it. Otherwise, - /// return `Err`. - fn insert_child_at_edge(&mut self, at_start: bool) -> Result>, DocError> { - let hole = self.ast.new_hole(); - match self.ast.inner() { - AstKind::Flexible(mut flexible) => { - let original_num_children = flexible.num_children(); - let index = if at_start { 0 } else { original_num_children }; - flexible - .insert_child(index, hole) - .map_err(|_| DocError::WrongSort)?; - flexible.goto_child(index); - let mut undo = Vec::new(); - if original_num_children != 0 { - // If there are still children left after removing this - // one, we won't automatically go back up to the parent. - // So do that here. - undo.push(TreeNavCmd::Parent.into()); - } - undo.push(TreeCmd::Remove.into()); - Ok(undo) - } - _ => Err(DocError::CannotInsert), - } - } - - /// If `before` is true, insert the new ast immediately to the left of this - /// this node. Otherwise, insert it immediately to the right. If the - /// insertion is successful, return the list of commands needed to undo it. - /// Otherwise, return `Err`. - fn insert_sibling(&mut self, before: bool) -> Result>, DocError> { - let hole = self.ast.new_hole(); - let i = self.ast.goto_parent(); - let insertion_index = if before { i } else { i + 1 }; - match self.ast.inner() { - AstKind::Fixed(mut fixed) => { - // Oops, go back, we can't insert something into a fixed node. - fixed.goto_child(i); - Err(DocError::CannotInsert) - } - AstKind::Flexible(mut flexible) => { - let result = flexible - .insert_child(insertion_index, hole) - .map_err(|_| DocError::WrongSort); - - if let Err(err) = result { - // Go back to the node we started on! - flexible.goto_child(i); - return Err(err); - } else { - // Go to the node we successfully inserted. - flexible.goto_child(insertion_index); - } - - if before && i != 0 { - Ok(vec![TreeNavCmd::Right.into(), TreeCmd::Remove.into()]) - } else { - Ok(vec![TreeCmd::Remove.into()]) - } - } - _ => panic!("how can a parent not be fixed or flexible?"), - } - } - - /// Replace the current node with the given node. If successful, return the - /// replaced node. If the given node cannot be placed here because it has - /// the wrong Sort, return it as an `Err`. - fn replace(&mut self, new_ast: Ast<'l>) -> Result, Ast<'l>> { - let i = self.ast.goto_parent(); // child index - match self.ast.inner() { - AstKind::Fixed(mut fixed) => { - let old_ast = fixed.replace_child(i, new_ast); - fixed.goto_child(i); - old_ast - } - AstKind::Flexible(mut flexible) => { - let old_ast = flexible.replace_child(i, new_ast); - flexible.goto_child(i); - old_ast - } - _ => panic!("how can a parent not be fixed or flexible?"), - } - } - - /// Entirely remove the current node, if possible (eg. if it has a flexible - /// parent). Return the list of commands required to undo the removal. - fn remove(&mut self) -> Result>, DocError> { - let i = self.ast.goto_parent(); - match self.ast.inner() { - AstKind::Fixed(mut fixed) => { - // Oops, go back, we can't remove a child from a fixed parent. - fixed.goto_child(i); - Err(DocError::CannotRemoveNode) - } - AstKind::Flexible(mut flexible) => { - let old_ast = flexible.remove_child(i); - let num_children = flexible.num_children(); - let undos = if num_children == 0 { - // Stay at the childless parent - vec![ - TreeCmd::Replace(old_ast).into(), - TreeCmd::InsertHolePrepend.into(), - ] - } else if i == 0 { - // We removed the first child, so to undo we must insert before. - flexible.goto_child(0); - vec![ - TreeCmd::Replace(old_ast).into(), - TreeCmd::InsertHoleBefore.into(), - ] - } else { - // Go to the left. - flexible.goto_child(i - 1); - vec![ - TreeCmd::Replace(old_ast).into(), - TreeCmd::InsertHoleAfter.into(), - ] - }; - Ok(undos) - } - _ => panic!("how can a parent not be fixed or flexible?"), - } - } -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs deleted file mode 100644 index 9e3400a..0000000 --- a/editor/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![allow(dead_code)] - -mod ast; -mod command; -mod doc; -mod notationset; -mod test_util; -mod text; - -pub use self::ast::{Ast, AstForest, AstRef}; -pub use self::command::{ - Command, EditorCmd, MetaCommand, TextCmd, TextNavCmd, TreeCmd, TreeNavCmd, -}; -pub use self::doc::{Clipboard, Doc, DocError}; -pub use self::notationset::{NotationSet, NotationSets}; -pub use test_util::{make_json_lang, make_singleton_lang_set, TestEditor}; diff --git a/editor/src/notationset.rs b/editor/src/notationset.rs deleted file mode 100644 index ac0fb5b..0000000 --- a/editor/src/notationset.rs +++ /dev/null @@ -1,175 +0,0 @@ -use lazy_static::lazy_static; -use std::collections::HashMap; - -use language::{ConstructName, Language, LanguageName}; -use pretty::notation_constructors::{child, literal}; -use pretty::{Notation, Style}; -use utility::GrowOnlyMap; - -pub struct NotationSet { - name: LanguageName, - notations: HashMap, -} - -pub type NotationSets = GrowOnlyMap; - -lazy_static! { - /// Notations for built-in constructs that can appear in any document. - pub static ref BUILTIN_NOTATIONS: HashMap = - vec![ - ("hole".into(), literal("?", Style::plain())), - ("root".into(), child(0)), - ].into_iter().collect(); -} - -impl NotationSet { - // TODO: validate against language - pub fn new(language: &Language, notations: Vec<(ConstructName, Notation)>) -> NotationSet { - let mut map = HashMap::new(); - for (construct, notation) in notations { - map.insert(construct, notation); - } - NotationSet { - name: language.name().to_owned(), - notations: map, - } - } - - pub fn hole() -> &'static Notation { - BUILTIN_NOTATIONS - .get(&"hole".into()) - .expect("no builtin 'hole' notation found") - } - - pub fn lookup(&self, construct: &ConstructName) -> &Notation { - match self.notations.get(construct) { - None => match BUILTIN_NOTATIONS.get(construct) { - None => panic!( - "Construct {:?} not found in notation set for {:?}", - construct, self.name - ), - Some(notation) => notation, - }, - - Some(notation) => notation, - } - } -} - -#[cfg(test)] -mod example { - use super::*; - use language::{Arity, Construct, Language}; - use pretty::notation_constructors::text; - use pretty::*; - - fn punct(s: &str) -> Notation { - literal(s, Style::color(Color::Base0A)) - } - - fn word(s: &str) -> Notation { - literal(s, Style::color(Color::Base0B)) - } - - fn txt() -> Notation { - text(Style { - color: Color::Base0D, - emph: Emph::underlined(), - reversed: false, - }) - } - - /// An example language for testing. - pub fn example_language() -> (Language, NotationSet) { - let mut language = Language::new("TestLang".into()); - - let arity = Arity::Fixed(vec!["Expr".into(), "Expr".into()]); - let construct = Construct::new("plus", "Expr", arity, Some('p')); - language.add(construct); - let plus_notation = - (child(0) + punct(" + ") + child(1)) | child(0) ^ (punct("+ ") + child(1)); - - let notation = NotationSet::new(&language, vec![("plus".into(), plus_notation)]); - (language, notation) - /* - let syn = repeat(Repeat{ - empty: empty(), - lone: star(), - first: star() + punct(", "), - middle: star() + punct(", "), - last: star() - }) | repeat(Repeat{ - empty: empty(), - lone: star(), - first: flush(star() + punct(",")), - middle: flush(star() + punct(",")), - last: star() - }); - lang.add('a', Construct::new("args", Arity::extendable(0), syn)); - - let syn = repeat(Repeat{ - empty: punct("[]"), - lone: punct("[") + star() + punct("]"), - first: punct("[") + star() + punct(", "), - middle: star() + punct(", "), - last: star() + punct("]") - })| repeat(Repeat{ - empty: punct("[]"), - lone: punct("[") + star() + punct("]"), - first: flush(star() + punct(",")), - middle: flush(star() + punct(",")), - last: star() + punct("]") - })| repeat(Repeat{ - empty: punct("[]"), - lone: punct("[") + star() + punct("]"), - first: punct("[") - + (star() + punct(", ") | flush(star() + punct(","))), - middle: star() + punct(", ") | flush(star() + punct(",")), - last: star() + punct("]") - }); - lang.add('l', Construct::new("list", Arity::extendable(0), syn)); - - let syn = - word("func ") + child(0) - + punct("(") + child(1) + punct(") { ") + child(2) + punct(" }") - | flush(word("func ") + child(0) + punct("(") + child(1) + punct(") {")) - + flush(word(" ") + child(2)) - + punct("}") - | flush(word("func ") + child(0) + punct("(")) - + flush(word(" ") + child(1) + punct(")")) - + flush(punct("{")) - + flush(word(" ") + child(2)) - + punct("}"); - lang.add('f', Construct::new("func", Arity::fixed(3), syn)); - - let syn = if_empty_text(txt() + punct("·"), txt()); - lang.add('i', Construct::new("iden", Arity::text(), syn)); - - let syn = punct("'") + txt() + punct("'"); - lang.add('s', Construct::new("strn", Arity::text(), syn)); - */ - } - - /* - pub fn example_tree<'l>(lang: &'l Language, tweak: bool) -> Tree<'l> { - let con_func = lang.lookup_name("func"); - let con_id = lang.lookup_name("iden"); - let con_str = lang.lookup_name("strn"); - let con_arg = lang.lookup_name("args"); - let con_plus = lang.lookup_name("plus"); - - let foo = Tree::new_text(con_id, "foo"); - let abc = Tree::new_text(con_id, "abc"); - let def = Tree::new_text(con_id, "def"); - let args = Tree::new_forest(con_arg, vec!(abc, def)); - let abcdef1 = Tree::new_text(con_str, "abcdef"); - let abcdef2 = if tweak { - Tree::new_text(con_str, "abc") - } else { - Tree::new_text(con_str, "abcdef") - }; - let body = Tree::new_forest(con_plus, vec!(abcdef1, abcdef2)); - Tree::new_forest(con_func, vec!(foo, args, body)) - } - */ -} diff --git a/editor/src/test_util/json_lang.rs b/editor/src/test_util/json_lang.rs deleted file mode 100644 index f52ee90..0000000 --- a/editor/src/test_util/json_lang.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::NotationSet; -use language::{Arity, Construct, Language}; -use pretty::notation_constructors::{child, literal, no_wrap, repeat, text}; -use pretty::{Color, Emph, Notation, RepeatInner, Style}; - -pub fn make_json_lang() -> (Language, NotationSet) { - let notations = vec![ - ("string".into(), json_string()), - ("number".into(), json_number()), - ("true".into(), json_boolean(true)), - ("false".into(), json_boolean(false)), - ("null".into(), json_null()), - ("list".into(), json_list()), - ("dict".into(), json_dict()), - ("key".into(), json_key()), - ("entry".into(), json_dict_entry()), - ]; - let constructs = vec![ - Construct::new("string", "Value", Arity::Text, Some('s')), - Construct::new("number", "Value", Arity::Text, Some('n')), - Construct::new("true", "Value", Arity::Fixed(Vec::new()), Some('t')), - Construct::new("false", "Value", Arity::Fixed(Vec::new()), Some('f')), - Construct::new("null", "Value", Arity::Fixed(Vec::new()), Some('x')), - Construct::new("list", "Value", Arity::Flexible("Value".into()), Some('l')), - Construct::new("dict", "Value", Arity::Flexible("Entry".into()), Some('d')), - Construct::new("key", "Key", Arity::Text, Some('k')), - Construct::new( - "entry", - "Entry", - Arity::Fixed(vec!["Key".into(), "Value".into()]), - Some('e'), - ), - ]; - // TODO: some of this boilerplate should get abstracted out - let mut lang = Language::new("json".into()); - for construct in constructs { - lang.add(construct); - } - let note_set = NotationSet::new(&lang, notations); - (lang, note_set) -} - -fn json_string() -> Notation { - let style = Style::color(Color::Base0B); - literal("\"", style) + text(style) + literal("\"", style) -} - -fn json_key() -> Notation { - let style = Style { - color: Color::Base0D, - emph: Emph::underlined(), - reversed: false, - }; - literal("'", style) + text(style) + literal("'", style) -} - -fn json_number() -> Notation { - let style = Style::color(Color::Base09); - Notation::IfEmptyText(Box::new(literal("·", style)), Box::new(text(style))) -} - -fn json_boolean(value: bool) -> Notation { - let color = Color::Base0E; - let (name, emph) = if value { - ( - "true", - Emph { - underlined: true, - bold: true, - }, - ) - } else { - ( - "false", - Emph { - underlined: false, - bold: true, - }, - ) - }; - literal( - name, - Style { - emph, - color, - reversed: false, - }, - ) -} - -fn json_null() -> Notation { - let style = Style { - color: Color::Base0E, - emph: Emph::plain(), - reversed: true, - }; - literal("null", style) -} - -/// If there is any multiline element, all elements must go on separate lines. -/// Otherwise, they can be grouped together on the same lines. -/// Put the opening and closing delimiters on the same lines as the first and last elements, respectively. -fn json_list() -> Notation { - let empty = punct("[]"); - let lone = punct("[") + child(0) + punct("]"); - repeat(RepeatInner { - empty: empty.clone(), - lone: lone.clone(), - join: (Notation::Left + punct(", ") + no_wrap(Notation::Right)) - | ((Notation::Left + punct(",")) ^ no_wrap(Notation::Right)), - surround: punct("[") + Notation::Surrounded + punct("]"), - }) | repeat(RepeatInner { - empty, - lone, - join: (Notation::Left + punct(",")) ^ Notation::Right, - surround: punct("[") + Notation::Surrounded + punct("]"), - }) -} - -/// Try putting the key and value on the same line. -/// If they don't fit, wrap after the colon, and indent the value. -fn json_dict_entry() -> Notation { - no_wrap(child(0) + punct(": ") + child(1)) | ((child(0) + punct(":")) ^ (indent() + child(1))) -} - -/// Put all entries on separate lines. -/// If there is more than one entry, put the opening and closing delimiters on separate lines too. -fn json_dict() -> Notation { - repeat(RepeatInner { - empty: punct("{}"), - lone: punct("{") + child(0) + punct("}"), - join: (Notation::Left + punct(",")) ^ Notation::Right, - surround: punct("{") ^ (indent() + Notation::Surrounded) ^ punct("}"), - }) -} - -fn punct(text: &str) -> Notation { - literal(text, Style::plain()) -} - -fn indent() -> Notation { - literal(" ", Style::plain()) -} diff --git a/editor/src/test_util/mod.rs b/editor/src/test_util/mod.rs deleted file mode 100644 index 1eb17a4..0000000 --- a/editor/src/test_util/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod json_lang; -mod test_editor; - -pub use json_lang::make_json_lang; -pub use test_editor::{make_singleton_lang_set, TestEditor}; diff --git a/editor/src/test_util/test_editor.rs b/editor/src/test_util/test_editor.rs deleted file mode 100644 index df9d5c4..0000000 --- a/editor/src/test_util/test_editor.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::{Ast, AstForest, AstRef, Clipboard, Doc, DocError, MetaCommand, NotationSet}; -use language::{ConstructName, Language, LanguageName, LanguageSet}; -use pretty::{ - Col, CursorVisibility, PlainText, Pos, PrettyDocument, PrettyWindow, RenderOptions, Row, - ScrollStrategy, WidthStrategy, -}; - -/// A simple wrapper around a Doc that makes it more convenient to write tests -/// that execute commands to edit the document and check if the document -/// is rendered correctly. -pub struct TestEditor<'l> { - pub doc: Doc<'l>, - pub clipboard: Clipboard<'l>, - forest: AstForest<'l>, - /// Which language in the `lang_set` to use for the `doc`. - lang_name: LanguageName, - /// Must at least contain the language `lang_name`. - lang_set: &'l LanguageSet, - /// Notations for the `lang_name` language. - note_set: &'l NotationSet, -} - -impl<'l> TestEditor<'l> { - /// Create a new TestEditor containing a Doc in the given language. - pub fn new( - lang_set: &'l LanguageSet, - note_set: &'l NotationSet, - lang_name: LanguageName, - ) -> Self { - let lang = lang_set.get(&lang_name).unwrap(); - let forest = AstForest::new(&lang_set); - - let doc = Doc::new( - "MyTestDoc", - forest.new_fixed_tree(&lang, lang.lookup_construct(&"root".into()), note_set), - ); - - TestEditor { - doc, - clipboard: Clipboard::new(), - forest, - note_set, - lang_set, - lang_name, - } - } - - /// Execute the given command or meta-command, and return its result. - pub fn exec(&mut self, cmd: T) -> Result<(), DocError> - where - T: Into>, - { - self.doc.execute(cmd.into(), &mut self.clipboard) - } - - /// Try to create a new node in the forest with the given construct name. - pub fn node(&self, construct_name: &ConstructName) -> Option> { - let lang = self.lang_set.get(&self.lang_name).unwrap(); - self.forest.new_tree(lang, construct_name, self.note_set) - } - - /// Render the Doc as a string, and assert that it's equal to the `expected` - /// string. Use a default width and doc position for rendering. - pub fn assert_render(&self, expected: &str) { - self.assert_render_with(expected, 80, ScrollStrategy::Fixed(Pos::zero())) - } - - /// Render the Doc as a string, and assert that it's equal to the `expected` - /// string. Use the given width and doc position for rendering. - pub fn assert_render_with(&self, expected: &str, width: Col, scroll_strategy: ScrollStrategy) { - let mut window = PlainText::new(Pos { - col: width, - row: Row::max_value() / 2, - }); - - let options = RenderOptions { - scroll_strategy, - width_strategy: WidthStrategy::Fixed(width), - cursor_visibility: CursorVisibility::Hide, - }; - - self.doc - .ast_ref() - .pretty_print(&mut window.pane().unwrap(), options) - .unwrap(); - assert_eq!(window.to_string(), expected) - } - - pub fn ast_ref<'a>(&'a self) -> AstRef<'l, 'a> { - self.doc.ast_ref() - } -} - -/// Create a new LanguageSet containing only the given Language. Return the -/// LanguageSet along with the name of the given Language. -pub fn make_singleton_lang_set(lang: Language) -> (LanguageSet, LanguageName) { - let lang_name = lang.name().to_owned(); - let lang_set = LanguageSet::new(); - lang_set.insert(lang_name.clone(), lang); - (lang_set, lang_name) -} diff --git a/editor/src/text.rs b/editor/src/text.rs deleted file mode 100644 index 02b1b2a..0000000 --- a/editor/src/text.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::iter; - -/// This enum provides a consistent interface to a piece of text, while allowing -/// various optimizations that depend on whether the text is actively being -/// edited or not. -#[derive(Clone, Debug)] -pub enum Text { - /// Text that is not currently being edited. - Inactive(String), - /// Text that is currently being edited. - Active(ActiveText), -} - -/// Text that is currently being edited. -#[derive(Clone, Debug)] -pub struct ActiveText(String); - -impl Text { - /// Construct a new `Text` in an inactive state. - pub fn new_inactive() -> Self { - Text::Inactive(String::new()) - } - - /// Switch from an inactive to active state, so the text can be edited. - /// - /// # Panics - /// - /// Panics if the `Text` is already active. - pub fn activate(&mut self) { - *self = match self { - Text::Inactive(s) => Text::Active(ActiveText(s.to_owned())), - _ => panic!("text is already active"), - } - } - - /// Switch from an active to inactive state, so the text can no longer be edited. - /// - /// # Panics - /// - /// Panics if the `Text` is already inactive. - pub fn deactivate(&mut self) { - *self = match self { - Text::Active(ActiveText(s)) => Text::Inactive(s.to_owned()), - _ => panic!("text is already inactive"), - } - } - - /// Get a reference to the underlying string. Works both when active and inactive. - // TODO this might not be possible if we decide to store active text in a fancier data structure... - pub fn as_str(&self) -> &str { - match self { - Text::Active(ref s) => &s.0, - Text::Inactive(ref s) => s, - } - } - - /// Return the length of the text in characters. Works both when active and inactive. - pub fn num_chars(&self) -> usize { - match self { - Text::Active(ref s) => s.num_chars(), - Text::Inactive(ref s) => s.chars().count(), - } - } - - /// Insert a new character at the given index. - /// - /// # Panics - /// - /// Panics if the `Text` is inactive or the index is greater than the number - /// of characters in the text. - pub fn insert(&mut self, char_index: usize, character: char) { - match self { - Text::Active(active_text) => active_text.insert(char_index, character), - _ => panic!("Text::insert - tried to edit inactive text"), - } - } - - /// Remove and return the character at the given index. - /// - /// # Panics - /// - /// Panics if the `Text` is inactive or the index is greater than the number - /// of characters in the text. - pub fn delete(&mut self, char_index: usize) -> char { - match self { - Text::Active(active_text) => active_text.delete(char_index), - _ => panic!("Text::delete - tried to edit inactive text"), - } - } - - /// Set the text to the given string, replacing the current contents. - /// - /// # Panics - /// - /// Panics if the `Text` is inactive. - pub fn set(&mut self, s: String) { - match self { - Text::Active(active_text) => active_text.set(s), - _ => panic!("Text::set - tried to edit inactive text"), - } - } -} - -impl AsRef for Text { - fn as_ref(&self) -> &str { - match self { - Text::Active(s) => &s.0, - Text::Inactive(s) => s, - } - } -} - -impl ActiveText { - fn num_chars(&self) -> usize { - self.0.chars().count() - } - - fn delete(&mut self, char_index: usize) -> char { - // If char_index is larger than that, let byte_index() panic! - self.0.remove(self.byte_index(char_index)) - } - - fn insert(&mut self, char_index: usize, character: char) { - self.0.insert(self.byte_index(char_index), character); - } - - fn set(&mut self, s: String) { - self.0 = s; - } - - fn byte_index(&self, char_index: usize) -> usize { - self.0 - .char_indices() - .map(|(i, _)| i) - .chain(iter::once(self.0.len())) - .nth(char_index) - .expect("ActiveText::byte_index - character index is out of range") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic(expected = "Text::insert - tried to edit inactive text")] - fn test_inactive_insert() { - let mut t = Text::new_inactive(); - t.insert(0, 'a'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_insert() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(1, 'c'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_insert2() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.insert(2, 'c'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_insert3() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.insert(3, 'd'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_delete() { - let mut t = Text::new_inactive(); - t.activate(); - t.delete(1); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_delete2() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.delete(2); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_delete3() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.delete(3); - } - - #[test] - #[allow(clippy::cognitive_complexity)] - fn test_active_edit() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'b'); - t.insert(0, 'a'); - t.insert(2, 'd'); - assert_eq!(t.as_str(), "abd"); - assert_eq!(t.num_chars(), 3); - assert_eq!(t.as_str(), "abd"); - assert_eq!(t.num_chars(), 3); - - t.insert(2, 'c'); - assert_eq!(t.as_str(), "abcd"); - - t.insert(4, 'e'); - assert_eq!(t.as_str(), "abcde"); - - t.deactivate(); - assert_eq!(t.num_chars(), 5); - assert_eq!(t.as_str(), "abcde"); - t.activate(); - assert_eq!(t.num_chars(), 5); - assert_eq!(t.as_str(), "abcde"); - - assert_eq!(t.delete(2), 'c'); - assert_eq!(t.as_str(), "abde"); - - assert_eq!(t.delete(1), 'b'); - assert_eq!(t.as_str(), "ade"); - - assert_eq!(t.delete(0), 'a'); - assert_eq!(t.num_chars(), 2); - assert_eq!(t.as_str(), "de"); - - assert_eq!(t.delete(1), 'e'); - assert_eq!(t.as_str(), "d"); - assert_eq!(t.num_chars(), 1); - - assert_eq!(t.delete(0), 'd'); - assert_eq!(t.num_chars(), 0); - assert_eq!(t.as_str(), ""); - - t.insert(0, 'a'); - assert_eq!(t.delete(0), 'a'); - assert_eq!(t.as_str(), ""); - assert_eq!(t.num_chars(), 0); - } -} diff --git a/editor/tests/test_json_doc.rs b/editor/tests/test_json_doc.rs deleted file mode 100644 index 34b94ab..0000000 --- a/editor/tests/test_json_doc.rs +++ /dev/null @@ -1,732 +0,0 @@ -use editor::{ - make_json_lang, make_singleton_lang_set, DocError, EditorCmd, MetaCommand, TestEditor, TextCmd, - TextNavCmd, TreeCmd, TreeNavCmd, -}; -use pretty::{Pos, ScrollStrategy}; - -/// Check if the expression matches the pattern, and panic with a informative -/// message if it doesn't. -macro_rules! assert_matches { - ($expression:expr, $pattern:pat) => { - if let $pattern = $expression { - () - } else { - panic!( - "assertion failed: `(expr matches pattern)`\n expr: {:?}\n pattern: {:?}", - $expression, - stringify!($pattern) - ) - } - }; -} - -// TODO: expand this into a comprehensive test suite - -/// Regression test for an old bug in the Clone implementation. -#[test] -fn test_tree_clone_panic() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"entry".into()).unwrap())) - .unwrap(); -} - -#[test] -fn test_json_undo_redo() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"null".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, null]"); - - ed.exec(TreeCmd::InsertHoleBefore).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - - ed.assert_render("[true, false, null]"); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true, null]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true]"); - - ed.exec(MetaCommand::Redo).unwrap(); - ed.assert_render("[true, null]"); - - ed.exec(MetaCommand::Redo).unwrap(); - ed.assert_render("[true, false, null]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true, null]"); - - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.assert_render("[true, null, []]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true, null]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("?"); - - ed.exec(MetaCommand::Redo).unwrap(); - ed.assert_render("[true]"); - - ed.exec(MetaCommand::Redo).unwrap(); - ed.assert_render("[true, null]"); - - ed.exec(MetaCommand::Redo).unwrap(); - ed.assert_render("[true, null, []]"); - - assert_matches!(ed.exec(MetaCommand::Redo), Err(DocError::NothingToRedo)); - ed.assert_render("[true, null, []]"); -} - -#[test] -fn test_json_cursor_at_top() { - let width = 7; - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"null".into()).unwrap())) - .unwrap(); - - ed.assert_render_with( - "[true,\n false,\n null]", - width, - ScrollStrategy::Fixed(Pos::zero()), - ); - - ed.assert_render_with( - " null]", - width, - ScrollStrategy::CursorHeight { fraction: 1.0 }, - ); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.assert_render_with( - " false,\n null]", - width, - ScrollStrategy::CursorHeight { fraction: 1.0 }, - ); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.assert_render_with( - "[true,\n false,\n null]", - width, - ScrollStrategy::CursorHeight { fraction: 1.0 }, - ); - - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.assert_render_with( - "[true,\n false,\n null]", - width, - ScrollStrategy::CursorHeight { fraction: 1.0 }, - ); -} - -#[test] -fn test_json_string() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"string".into()).unwrap())) - .unwrap(); - assert!(ed.doc.in_tree_mode()); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - assert!(!ed.doc.in_tree_mode()); - - ed.exec(TextNavCmd::TreeMode).unwrap(); - assert!(ed.doc.in_tree_mode()); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - assert!(!ed.doc.in_tree_mode()); - - assert_matches!( - ed.exec(TextCmd::DeleteCharForward), - Err(DocError::CannotDeleteChar) - ); - assert_matches!( - ed.exec(TextCmd::DeleteCharBackward), - Err(DocError::CannotDeleteChar) - ); - assert_matches!(ed.exec(TextNavCmd::Left), Err(DocError::CannotMove)); - assert_matches!(ed.exec(TextNavCmd::Right), Err(DocError::CannotMove)); - - ed.exec(TextCmd::InsertChar('a')).unwrap(); - ed.assert_render("[\"a\"]"); - - assert_matches!( - ed.exec(TextCmd::DeleteCharForward), - Err(DocError::CannotDeleteChar) - ); - assert_matches!(ed.exec(TextNavCmd::Right), Err(DocError::CannotMove)); - ed.exec(TextNavCmd::Left).unwrap(); - assert_matches!(ed.exec(TextNavCmd::Left), Err(DocError::CannotMove)); - ed.exec(TextNavCmd::Right).unwrap(); - assert_matches!(ed.exec(TextNavCmd::Right), Err(DocError::CannotMove)); - - ed.exec(TextCmd::DeleteCharBackward).unwrap(); - ed.assert_render("[\"\"]"); - - ed.exec(TextCmd::InsertChar('a')).unwrap(); - ed.exec(TextCmd::InsertChar('b')).unwrap(); - ed.exec(TextCmd::InsertChar('c')).unwrap(); - ed.assert_render("[\"abc\"]"); - - assert_matches!(ed.exec(TextNavCmd::Right), Err(DocError::CannotMove)); - ed.exec(TextNavCmd::Left).unwrap(); - ed.exec(TextNavCmd::Left).unwrap(); - ed.exec(TextNavCmd::Left).unwrap(); - assert_matches!(ed.exec(TextNavCmd::Left), Err(DocError::CannotMove)); - ed.exec(TextCmd::DeleteCharForward).unwrap(); - ed.assert_render("[\"bc\"]"); - - ed.exec(TextNavCmd::Right).unwrap(); - ed.exec(TextCmd::InsertChar('d')).unwrap(); - ed.assert_render("[\"bdc\"]"); - - ed.exec(TextCmd::DeleteCharForward).unwrap(); - ed.assert_render("[\"bd\"]"); - - assert_matches!( - ed.exec(TextCmd::DeleteCharForward), - Err(DocError::CannotDeleteChar) - ); - - ed.exec(TextCmd::DeleteCharBackward).unwrap(); - ed.assert_render("[\"b\"]"); - - ed.exec(TextCmd::DeleteCharBackward).unwrap(); - ed.assert_render("[\"\"]"); - - assert_matches!( - ed.exec(TextCmd::DeleteCharBackward), - Err(DocError::CannotDeleteChar) - ); -} - -#[test] -fn test_insert() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.assert_render("[]"); - - // Cursor is now on the `[]`, and its parent (the root) isn't flexible: - assert_matches!( - ed.exec(TreeCmd::InsertHoleBefore), - Err(DocError::CannotInsert) - ); - assert_matches!( - ed.exec(TreeCmd::InsertHoleAfter), - Err(DocError::CannotInsert) - ); - - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.assert_render("[?]"); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.assert_render("[true]"); - - // Cursor is now on the `true`, which isn't flexible: - assert_matches!( - ed.exec(TreeCmd::InsertHolePrepend), - Err(DocError::CannotInsert) - ); - assert_matches!( - ed.exec(TreeCmd::InsertHolePostpend), - Err(DocError::CannotInsert) - ); - - ed.exec(TreeCmd::InsertHoleBefore).unwrap(); - ed.assert_render("[?, true]"); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.assert_render("[false, true]"); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.assert_render("[false, ?, true]"); - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.assert_render("[false, ?, true, ?]"); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.assert_render("[false, ?, true, []]"); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[false, ?, true, [?]]"); - ed.exec(TreeCmd::Replace(ed.node(&"dict".into()).unwrap())) - .unwrap(); - ed.assert_render("[false, ?, true, [{}]]"); - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[false, ?, true, [{}, ?]]"); - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.assert_render("[false, ?, true, [?, {}, ?]]"); - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.assert_render("[false, ?, true, [?, {}, ?], ?]"); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(TreeNavCmd::Child(1)).unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.assert_render("[false, ?, true, [?, {?}, ?], ?]"); - ed.exec(TreeCmd::Replace(ed.node(&"entry".into()).unwrap())) - .unwrap(); - ed.assert_render("[false, ?, true, [?, {?: ?}, ?], ?]"); - ed.exec(TreeNavCmd::Child(1)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"null".into()).unwrap())) - .unwrap(); - ed.assert_render("[false, ?, true, [?, {?: null}, ?], ?]"); - - // Can't do any type of insertion when cursor is on dict entry key. - assert_matches!( - ed.exec(TreeCmd::InsertHolePrepend), - Err(DocError::CannotInsert) - ); - assert_matches!( - ed.exec(TreeCmd::InsertHolePostpend), - Err(DocError::CannotInsert) - ); - assert_matches!( - ed.exec(TreeCmd::InsertHoleBefore), - Err(DocError::CannotInsert) - ); - assert_matches!( - ed.exec(TreeCmd::InsertHoleAfter), - Err(DocError::CannotInsert) - ); - - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeCmd::InsertHoleBefore).unwrap(); - ed.assert_render( - r#"[false, - ?, - true, - [?, - { - ?, - ?: null - }, - ?], - ?]"#, - ); -} - -#[test] -fn test_remove() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.assert_render("[]"); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[?]"); - ed.exec(TreeCmd::Remove).unwrap(); - ed.assert_render("[]"); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.assert_render("[?]"); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.assert_render("[true]"); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.assert_render("[true, ?]"); - ed.exec(TreeCmd::Remove).unwrap(); - ed.assert_render("[true]"); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.assert_render("[true, ?]"); - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(TreeCmd::Remove).unwrap(); - ed.assert_render("[?]"); - - ed.exec(TreeCmd::Replace(ed.node(&"dict".into()).unwrap())) - .unwrap(); - ed.assert_render("[{}]"); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[{?}]"); - ed.exec(TreeCmd::Remove).unwrap(); - ed.assert_render("[{}]"); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[{?}]"); - ed.exec(TreeCmd::Replace(ed.node(&"entry".into()).unwrap())) - .unwrap(); - ed.assert_render("[{?: ?}]"); - ed.exec(TreeNavCmd::Child(0)).unwrap(); - assert_matches!(ed.exec(TreeCmd::Remove), Err(DocError::CannotRemoveNode)); - ed.exec(TreeNavCmd::Right).unwrap(); - assert_matches!(ed.exec(TreeCmd::Remove), Err(DocError::CannotRemoveNode)); - ed.assert_render("[{?: ?}]"); - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeCmd::Remove).unwrap(); - ed.assert_render("[]"); - assert_matches!(ed.exec(TreeCmd::Remove), Err(DocError::CannotRemoveNode)); - ed.assert_render("[]"); -} - -#[test] -fn test_cut_copy_paste() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.assert_render("?"); - assert_eq!(ed.clipboard.len(), 0); - - assert_matches!(ed.exec(EditorCmd::PasteSwap), Err(DocError::EmptyClipboard)); - ed.assert_render("?"); - assert_eq!(ed.clipboard.len(), 0); - - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.assert_render("true"); - - ed.exec(EditorCmd::Cut).unwrap(); - ed.assert_render("?"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.assert_render("[?]"); - - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.assert_render("[true]"); - assert_eq!(ed.clipboard.len(), 1); // Contains a hole - - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.assert_render("[true, ?]"); - - // Swap the two holes, which is uninteresting - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.assert_render("[true, ?]"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(EditorCmd::PopClipboard).unwrap(); - assert_eq!(ed.clipboard.len(), 0); - assert_matches!( - ed.exec(EditorCmd::PopClipboard), - Err(DocError::EmptyClipboard) - ); - assert_eq!(ed.clipboard.len(), 0); - - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"null".into()).unwrap())) - .unwrap(); - ed.assert_render("[true, false, null]"); - - ed.exec(EditorCmd::Cut).unwrap(); - ed.assert_render("[true, false, ?]"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(EditorCmd::Cut).unwrap(); - ed.assert_render("[true, ?, ?]"); - assert_eq!(ed.clipboard.len(), 2); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.assert_render("[false, ?, ?]"); - assert_eq!(ed.clipboard.len(), 2); - - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(EditorCmd::PopClipboard).unwrap(); - ed.assert_render("[false, ?, true]"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.assert_render("[false, null, true]"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(EditorCmd::Copy).unwrap(); - ed.assert_render("[false, null, true]"); - assert_eq!(ed.clipboard.len(), 2); - - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[false, null, true, ?]"); - - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.assert_render("[false, null, true, [false, null, true]]"); - assert_eq!(ed.clipboard.len(), 2); -} - -#[test] -fn test_clear() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.assert_render("?"); - - ed.exec(TreeCmd::Clear).unwrap(); - ed.assert_render("?"); - - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.assert_render("[true, false]"); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(TreeCmd::Clear).unwrap(); - ed.assert_render("[?, false]"); - assert_eq!(ed.clipboard.len(), 0); - - ed.exec(TreeCmd::Replace(ed.node(&"dict".into()).unwrap())) - .unwrap(); - ed.assert_render("[{}, false]"); - - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.assert_render("[{?}, false]"); - ed.exec(TreeCmd::Replace(ed.node(&"entry".into()).unwrap())) - .unwrap(); - ed.assert_render("[{?: ?}, false]"); - - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeNavCmd::Parent).unwrap(); - ed.exec(TreeCmd::Clear).unwrap(); - ed.assert_render("?"); -} - -#[test] -fn test_undo_clipboard() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - assert_eq!(ed.clipboard.len(), 0); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, false]"); - - ed.exec(EditorCmd::Cut).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, ?]"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true, false]"); - assert_eq!(ed.clipboard.len(), 1); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[false, false]"); - assert_eq!(ed.clipboard.len(), 1); // contains `true` - - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[false, ?, false]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[false, false]"); - assert_eq!(ed.clipboard.len(), 1); // contains `true` - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true, false]"); - assert_eq!(ed.clipboard.len(), 1); // contains `true` - - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, false, ?]"); - - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, false, true]"); - assert_eq!(ed.clipboard.len(), 1); // contains hole - - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, false, ?]"); - assert_eq!(ed.clipboard.len(), 1); // contains `true` - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[true, false, true]"); - assert_eq!(ed.clipboard.len(), 1); // contains `true` - - ed.exec(MetaCommand::Redo).unwrap(); - ed.assert_render("[true, false, ?]"); - assert_eq!(ed.clipboard.len(), 1); // contains `true` - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, true, ?]"); - assert_eq!(ed.clipboard.len(), 1); // contains `false` - - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, true, false]"); - assert_eq!(ed.clipboard.len(), 1); // contains hole -} - -#[test] -fn test_bookmark() { - let (lang, note_set) = make_json_lang(); - let (lang_set, lang_name) = make_singleton_lang_set(lang); - let mut ed = TestEditor::new(&lang_set, ¬e_set, lang_name); - - ed.exec(TreeNavCmd::Child(0)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(TreeCmd::InsertHolePostpend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"true".into()).unwrap())) - .unwrap(); - - let mark_true = ed.doc.bookmark(); - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - let mark_false = ed.doc.bookmark(); - - ed.exec(TreeCmd::InsertHoleAfter).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - let mark_list = ed.doc.bookmark(); - - ed.exec(TreeCmd::InsertHolePrepend).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"null".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - let mark_null = ed.doc.bookmark(); - - ed.assert_render("[true, false, [null]]"); - - ed.exec(TreeNavCmd::GotoBookmark(mark_true)).unwrap(); - ed.exec(TreeCmd::InsertHoleBefore).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[?, true, false, [null]]"); - - ed.exec(TreeNavCmd::GotoBookmark(mark_null)).unwrap(); - ed.exec(TreeCmd::Clear).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[?, true, false, [?]]"); - - ed.exec(TreeNavCmd::GotoBookmark(mark_false)).unwrap(); - ed.exec(TreeNavCmd::GotoBookmark(mark_false)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"false".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[?, true, false, [?]]"); - - assert_matches!( - ed.exec(TreeNavCmd::GotoBookmark(mark_false)), - Err(DocError::CannotMove) - ); - assert_matches!( - ed.exec(TreeNavCmd::GotoBookmark(mark_null)), - Err(DocError::CannotMove) - ); - ed.exec(TreeNavCmd::GotoBookmark(mark_list)).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"list".into()).unwrap())) - .unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[?, true, false, []]"); - - ed.exec(TreeNavCmd::GotoBookmark(mark_true)).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(EditorCmd::Cut).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[?, ?, false, []]"); - - ed.exec(TreeNavCmd::Left).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.exec(EditorCmd::PasteSwap).unwrap(); - ed.exec(MetaCommand::EndGroup).unwrap(); - ed.assert_render("[true, ?, false, []]"); - - // Cut does not preserve bookmarks - assert_matches!( - ed.exec(TreeNavCmd::GotoBookmark(mark_true)), - Err(DocError::CannotMove) - ); - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[?, ?, false, []]"); - - ed.exec(MetaCommand::Undo).unwrap(); - ed.assert_render("[?, true, false, []]"); - - // Undo preserves bookmarks - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(TreeNavCmd::Right).unwrap(); - ed.exec(TreeNavCmd::GotoBookmark(mark_true)).unwrap(); - ed.exec(TreeCmd::Replace(ed.node(&"null".into()).unwrap())) - .unwrap(); - ed.assert_render("[?, null, false, []]"); -} diff --git a/experiment/experimental_tree.rs b/experiment/experimental_tree.rs deleted file mode 100644 index 894c05f..0000000 --- a/experiment/experimental_tree.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::ops::{Index, IndexMut}; - -use self::Tree::{Forest, Text}; - -pub trait TreeNode { - fn empty() -> Self; - fn update_text(&mut self, text: &str); - fn update_forest(&mut self, child_nodes: Vec<&Self>); -} - -pub enum Tree { - Forest(Node, Vec>), - Text(Node, String) -} - -impl Tree { - - // Tree Constructors // - - /// Construct a new foresty tree. - pub fn new_forest(node: Node, children: Vec>) -> Tree { - let mut tree = Forest(node, children); - tree.update(); - tree - } - - /// Construct a new texty tree. - pub fn new_text(node: Node, text: String) -> Tree { - let mut tree = Text(node, text); - tree.update(); - tree - } - - - // Alll Trees // - - pub fn cursor(&mut self) -> Cursor { - Cursor{ - path: vec!(self) - } - } - - - // Texty Trees // - - /// Is this tree texty? - pub fn is_texty(&self) -> bool { - match self { - &Forest(_, _) => false, - &Text(_, _) => true - } - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text(&self) -> &String { - match self { - &Forest(_, _) => error_not_texty(), - &Text(_, ref text) => text - } - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text_mut(&mut self) -> &mut String { - match self { - &mut Forest(_, _) => error_not_texty(), - &mut Text(_, ref mut text) => text - } - } - - - // Foresty Trees // - - /// Is this tree foresty? - pub fn is_foresty(&self) -> bool { - match self { - &Forest(_, _) => true, - &Text(_, _) => false - } - } - - /// Return the children of a foresty tree. Panics on texty trees. - pub fn forest(&self) -> &Vec> { - match self { - &Forest(_, ref forest) => forest, - &Text(_, _) => error_not_foresty() - } - } - - /// Return the children of a foresty tree. Panics on texty trees. - pub fn forest_mut(&mut self) -> &mut Vec> { - match self { - &mut Forest(_, ref mut forest) => forest, - &mut Text(_, _) => error_not_foresty() - } - } - - - // Private // - - fn node(&self) -> &Node { - match self { - &Forest(ref node, _) => node, - &Text(ref node, _) => node - } - } - - /// *This must be called every time this tree or one of its - /// children is modifed.* - fn update(&mut self) { - match self { - &mut Forest(ref mut node, ref forest) => { - let nodes = forest.iter().map(|tree| tree.node()).collect(); - node.update_forest(nodes); - } - &mut Text(ref mut node, ref text) => { - node.update_text(text); - } - } - } -} - - -// Cursor // - -pub struct Cursor<'t, Node : TreeNode + 't> { - path: Vec<&'t mut Tree> // Only the last cell is active. -} - -impl<'t, Node : TreeNode + 't> Cursor<'t, Node> { - pub fn goto_parent(&mut self) { - if self.path.len() <= 1 { - error_root_parent(); - } - self.path.pop(); - } - - pub fn goto_child(&mut self, i: usize) { - unsafe { - let raw_ptr: *mut Tree = self.tree_mut(); - let mut_ref: &mut Tree = raw_ptr.as_mut().unwrap(); - self.path.push(&mut mut_ref[i]); - } - } - - pub fn root(&self) -> &Tree { - self.path.first().expect("empty cursor") - } - - pub fn tree(&self) -> &Tree { - self.path.last().expect("empty cursor") - } - - pub fn tree_mut(&mut self) -> &mut Tree { - self.path.last_mut().expect("empty cursor") - } -} - - -// Index // - -impl Index for Tree { - type Output = Tree; - fn index(&self, index: usize) -> &Tree { - match self { - &Forest(_, ref forest) => { - match forest.get(index) { - None => error_invalid_index(), - Some(tree) => tree - } - } - &Text(_, _) => error_invalid_index() - } - } -} - -impl IndexMut for Tree { - fn index_mut(&mut self, index: usize) -> &mut Tree { - match self { - &mut Forest(_, ref mut forest) => { - match forest.get_mut(index) { - None => error_invalid_index(), - Some(tree) => tree - } - } - &mut Text(_, _) => error_invalid_index() - } - } -} - - -// Errors // - -fn error_not_texty() -> ! { - panic!("tree - expected a texty tree!"); -} - -fn error_not_foresty() -> ! { - panic!("tree - expected a foresty tree!"); -} - -fn error_invalid_index() -> ! { - panic!("tree - invalid child index!"); -} - -fn error_root_parent() -> ! { - panic!("tree cursor - cannot take parent of root!"); -} - - - -/* -#[cfg(test)] -pub fn example_tree() -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "hi"), - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "world"))))) -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_trees() { - let mut tree = example_tree(); - assert_eq!(tree.len(), 2); - assert_eq!(tree[0].len(), 3); - assert_eq!(tree[1].len(), 1); - assert_eq!(tree[&vec!()].len(), 2); - assert_eq!(tree[&vec!(0)].len(), 3); - assert_eq!(tree[&vec!(1)].len(), 1); - assert_eq!(tree[&vec!(1, 0)].len(), 6); - - { - let root = &tree; - let hi = &tree[0]; - - assert!(root.is_foresty()); - assert!(!root.is_texty()); - assert!(!hi.is_foresty()); - assert!(hi.is_texty()); - - assert_eq!(hi.text().as_str(), "hi"); - assert_eq!(root.forest().len(), 2); - } - { - let world = &mut tree[1][0]; - assert_eq!(world.text_mut().as_str(), "world"); - } - { - let sub = &mut tree[1]; - assert_eq!(sub.forest_mut().len(), 1); - } - } - - #[test] - #[should_panic(expected = "expected a texty tree")] - fn test_text_panic() { - example_tree().text(); - } - - #[test] - #[should_panic(expected = "expected a foresty tree")] - fn test_forest_panic() { - example_tree()[0].forest(); - } -} -*/ diff --git a/experiment/gom.rs b/experiment/gom.rs deleted file mode 100644 index d7393e7..0000000 --- a/experiment/gom.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::cell::UnsafeCell; -use std::borrow::Borrow; -use std::sync::Mutex; - -/// Like a HashMap, but it can only get bigger. -/// It is (hopefully) safe to extend -pub struct GrowOnlyMap { - // The Mutex is to make sure that two different threads aren't - // mucking around with the UnsafeCell at once. (I want to ensure - // that `insert` and `get` are never being invoked - // simultaneously.) - - // The `UnsafeCell` is necessary because the *values* in the - // vector are immutable, while the vector *itself* is mutable, - // and an UnsafeCell is the only way to allow immutable references - // to within this mutable vector. - - // I'm using a vector of pairs instead of a HashMap because - // HashMaps might do tricksy things that I don't know about. - - // The values are Boxed because as the vector grows, it might - // re-allocate. The extra level of indirection will ensure that - // even though the Box moves, the value V inside it doesn't, thus - // keeping references to it valid. - mutex: Mutex)>>> -} - - -impl GrowOnlyMap { - pub fn new() -> GrowOnlyMap { - GrowOnlyMap { - mutex: Mutex::new(UnsafeCell::new(vec!())) - } - } - - /// Insert a key-value pair into the map. - /// - /// If the key is already in the map, this will have no effect. - pub fn insert(&self, key: K, value: V) { - let cell = self.mutex.lock().expect("GrowOnlyVector: mutex error"); - let vec = unsafe { - &mut *cell.get() - }; - for &(ref k, ref value) in &*vec { - if k == &key { - return; - } - } - vec.push((key, Box::new(value))); - } - - /// Get a reference to the value corresponding to the key. - pub fn get(&self, key: &Q) -> Option<&V> - where K: Borrow, Q: Eq - { - let cell = self.mutex.lock().expect("GrowOnlyVector: mutex error"); - let vec = unsafe { - &*cell.get() - }; - for &(ref k, ref value) in vec { - if key == k.borrow() { - return Some(value); - } - } - None - } -} - - -#[test] -fn test_grow_only_map() { - let mut junk = vec!(); - let mut map = GrowOnlyMap::new(); - - map.insert("hello".to_string(), - ("hello".to_string(), "hello".to_string())); - let h1 = &map.get("hello").unwrap().0[1..]; - assert_eq!(h1, "ello"); - - map.insert("hello".to_string(), - ("hey".to_string(), "hey".to_string())); - let h2 = &map.get("hello").unwrap().0[1..]; - assert_eq!(h1, "ello"); - assert_eq!(h2, "ello"); - - map.insert("world".to_string(), - ("there".to_string(), "world".to_string())); - let t = &map.get("world").unwrap().0; - let w = &map.get("world").unwrap().1[2..]; - let pair = &map.get("world").unwrap(); - assert_eq!(h1, "ello"); - assert_eq!(h2, "ello"); - assert_eq!(t, "there"); - assert_eq!(w, "rld"); - assert_eq!(&pair.0, "there"); - assert_eq!(&pair.1, "world"); - - for _ in 0..100000 { - map.insert("stuff".to_string(), - ("s".to_string(), "s".to_string())); - junk.push("junk".to_string()); - } - let s = &map.get("stuff").unwrap().0; - assert_eq!(h1, "ello"); - assert_eq!(h2, "ello"); - assert_eq!(t, "there"); - assert_eq!(w, "rld"); - assert_eq!(s, "s"); - assert_eq!(&pair.0, "there"); - assert_eq!(&pair.1, "world"); -} diff --git a/experiment/language.rs b/experiment/language.rs deleted file mode 100644 index 2263dc1..0000000 --- a/experiment/language.rs +++ /dev/null @@ -1,151 +0,0 @@ -// TODO: use or remove commented code - -//! An editable language. - -use std::collections::HashMap; -use std::iter::Iterator; -use super::construct::{ConstructName, Sort, Construct}; - -pub type LanguageName = String; - -/// The syntax and whatnot for a language. -pub struct Language { - name: LanguageName, - constructs: HashMap, - sorts: HashMap>, - keymap: HashMap -} - -impl Language { - - pub fn new(name: &str) -> Language { - Language { - name: name.to_string(), - sorts: HashMap::new(), - constructs: HashMap::new(), - keymap: HashMap::new() - } - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn add(&mut self, construct: Construct) { - // Insert sort - if !self.sorts.contains_key(&construct.sort) { - self.sorts.insert(construct.sort.clone(), vec!()); - } - self.sorts.get_mut(&construct.sort).unwrap().push(construct.name.clone()); - // Insert key - self.keymap.insert(construct.key, construct.name.clone()); - // Insert construct - self.constructs.insert(construct.name.clone(), construct); - } - - pub fn lookup_key(&self, key: char) -> Option<&Construct> { - match self.keymap.get(&key) { - Some(name) => Some(self.lookup_construct(name)), - None => None - } - } - - pub fn lookup_construct(&self, construct_name: &str) -> &Construct { - match self.constructs.get(construct_name) { - Some(con) => con, - None => panic!("Could not find construct named {} in language.", - construct_name) - } - } - - pub fn constructs(&self) -> impl Iterator { - self.constructs.values() - } - - pub fn has_sort(construct: &Construct, sort: &str) -> bool { - match self.sorts.get(sort) { - Some(constructs) => constructs.iter().any(|s| s == sort), - None => panic!("Could not find sort named {} in language.", sort) - } - } -} - -#[cfg(test)] -use self::example::*; - -#[cfg(test)] -mod example { - use language::construct::Arity; - use super::*; - - /// An example language for testing. - pub fn example_language() -> Language { - let mut language = Language::new("TestLang"); - - let arity = Arity::Forest(vec!("Expr".to_string(), - "Expr".to_string()), - None); - let construct = Construct::new("plus", "Expr", arity, 'p'); - language.add(construct); -/* - let syn = repeat(Repeat{ - empty: empty(), - lone: star(), - first: star() + punct(", "), - middle: star() + punct(", "), - last: star() - }) | repeat(Repeat{ - empty: empty(), - lone: star(), - first: flush(star() + punct(",")), - middle: flush(star() + punct(",")), - last: star() - }); - lang.add('a', Construct::new("args", Arity::extendable(0), syn)); - - let syn = repeat(Repeat{ - empty: punct("[]"), - lone: punct("[") + star() + punct("]"), - first: punct("[") + star() + punct(", "), - middle: star() + punct(", "), - last: star() + punct("]") - })| repeat(Repeat{ - empty: punct("[]"), - lone: punct("[") + star() + punct("]"), - first: flush(star() + punct(",")), - middle: flush(star() + punct(",")), - last: star() + punct("]") - })| repeat(Repeat{ - empty: punct("[]"), - lone: punct("[") + star() + punct("]"), - first: punct("[") - + (star() + punct(", ") | flush(star() + punct(","))), - middle: star() + punct(", ") | flush(star() + punct(",")), - last: star() + punct("]") - }); - lang.add('l', Construct::new("list", Arity::extendable(0), syn)); - - let syn = - word("func ") + child(0) - + punct("(") + child(1) + punct(") { ") + child(2) + punct(" }") - | flush(word("func ") + child(0) + punct("(") + child(1) + punct(") {")) - + flush(word(" ") + child(2)) - + punct("}") - | flush(word("func ") + child(0) + punct("(")) - + flush(word(" ") + child(1) + punct(")")) - + flush(punct("{")) - + flush(word(" ") + child(2)) - + punct("}"); - lang.add('f', Construct::new("func", Arity::fixed(3), syn)); - - let syn = if_empty_text(txt() + punct("·"), txt()); - lang.add('i', Construct::new("iden", Arity::text(), syn)); - - let syn = punct("'") + txt() + punct("'"); - lang.add('s', Construct::new("strn", Arity::text(), syn)); - */ - - language - } - -} diff --git a/experiment/mod.rs b/experiment/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/experiment/tree/cursor.rs b/experiment/tree/cursor.rs deleted file mode 100644 index abb61e9..0000000 --- a/experiment/tree/cursor.rs +++ /dev/null @@ -1,503 +0,0 @@ -use std::fmt; - -use syntax::Construct; -use tree::{Path, Tree, TreeRef, TreeMut}; - -use self::Mode::*; - -/// Indicates whether the cursor is currently editing a tree or text. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Mode { - TreeMode, - TextMode -} - -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &TreeMode => write!(f, "Tree"), - &TextMode => write!(f, "Text") - } - } -} - -/// The cursor navigates and edits a document. -pub struct Cursor<'t, 'l : 't> { - char_index: Option, - tree: TreeMut<'t, 'l> -} - -impl<'t, 'l : 't> Cursor<'t, 'l> { - - pub fn new(tree: &'t mut Tree<'l>) -> Cursor<'t, 'l> { - Cursor{ - char_index: None, - tree: tree.as_mut() - } - } - - // Info // - - /// The root of the document. - pub fn root(&'t self) -> TreeRef<'t, 'l> { - self.tree.as_ref().root() - } - - /// The tree at the current cursor location. - pub fn as_ref(&'t self) -> TreeRef<'t, 'l> { - self.tree.as_ref() - } - - /// The current cursor location. - pub fn path(&self) -> Path { - self.tree.path().clone() - } - - /// If editing text, the character index, else None. - pub fn char_index(&self) -> Option { - self.char_index - } - - // Tree Navigation // - - /// Attempt to move right (i.e., to the next tree sibling). - /// - /// This will fail if the cursor is in text mode, - /// or if there is no right sibling. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn right(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.has_parent() { - let i = self.tree.index(); - if i + 1 < self.tree.num_siblings() { - self.tree.goto_parent(); - self.tree.goto_child(i + 1); - return true; - } - } - false - } - - /// Attempt to move left (i.e., to the previous tree sibling). - /// - /// This will fail if the cursor is in text mode, - /// or if there is no left sibling. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn left(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.has_parent() { - let i = self.tree.index(); - if i > 0 { - self.tree.goto_parent(); - self.tree.goto_child(i - 1); - return true; - } - } - false - } - - /// Attempt to move up (i.e., to the parent node). - /// - /// This will fail if the cursor is in text mode, - /// or if it is at the root of the document. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn up(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.has_parent() { - let index = self.tree.goto_parent(); - *self.tree.breadcrumb_mut() = index; - return true; - } - false - } - - /// Attempt to move down (i.e., to a child). - /// - /// This will go to the child that was last visited. - /// It will fail if the cursor is in text mode, - /// or if it is at a tree that has no children. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn down(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.is_foresty() - && self.tree.num_children() > 0 - { - let index = self.tree.breadcrumb(); - self.tree.goto_child(index); - return true; - } - false - } - - // Text Navigation // - - /// Attempt to move right one character. - /// - /// This will fail if the cursor is in tree mode, - /// or if it is at the end of the text. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn right_char(&mut self) -> bool { - if let Some(i) = self.char_index { - let text = self.tree.text(); - if i < text.chars().count() { - self.char_index = Some(i + 1); - return true; - } - } - false - } - - /// Attempt to move left one character. - /// - /// This will fail if the cursor is in tree mode, - /// or if it is at the beginning of the text. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn left_char(&mut self) -> bool { - if let Some(i) = self.char_index { - if i > 0 { - self.char_index = Some(i - 1); - return true; - } - } - false - } - - // Modes // - - /// Is the cursor currently selecting a tree, or text? - pub fn mode(&self) -> Mode { - if self.char_index.is_some() { - TextMode - } else { - TreeMode - } - } - - /// Attempt to enter text mode. - /// - /// This will go to the last selected character position in the text - /// (or the end if it has not been selected yet). - /// It will fail if the cursor is already in text mode, - /// or if the tree selected is not texty. - /// - /// Returns `true` if the mode change was successful, or `false` otherwise. - pub fn enter_text(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.is_texty() { - self.char_index = Some(self.tree.breadcrumb()); - return true; - } - false - } - - /// Attempt to exit text mode. - /// - /// This will fail if the cursor is already in tree mode. - /// - /// Returns `true` if the mode change was successful, or `false` otherwise. - pub fn exit_text(&mut self) -> bool { - if self.mode() == TextMode { - *self.tree.breadcrumb_mut() = self.char_index.unwrap(); - self.char_index = None; - return true; - } - false - } - - // Tree Operations - - /// Add a child to the end of an extendable tree. - /// - /// This will fail if the cursor is in text mode. - /// - /// Returns `true` if successful. - pub fn add_child(&mut self) -> bool { - if self.tree.is_foresty() && self.tree.node().is_extendable() { - // Unsafe because .forest_mut() requires an update. - unsafe { - self.tree.forest_mut().push(Tree::new_hole()); - } - // Here's the update. - self.update(); - return true; - } - false - } - - /// Replace the selected tree with a new empty tree. - /// - /// This will fail if the cursor is in text mode. - /// - /// Returns the tree that was replaced. - pub fn replace_tree(&mut self, construct: &'l Construct) -> Option> { - if self.mode() == TreeMode { - let tree = self.tree.replace(Tree::new(construct)); - self.update(); - return Some(tree); - } - None - } - - /// Delete the selected tree. - /// - /// This will fail if the cursor is in text mode. - /// - /// Returns the tree that was deleted. - pub fn delete_tree(&mut self) -> Option> { - if self.mode() == TreeMode { - let tree = self.tree.replace(Tree::new_hole()); - self.update(); - return Some(tree); - } - None - } - - // Text Operations - - /// Insert a character after the selected position. - /// - /// This will fail if the cursor is in tree mode. - /// - /// Returns `true` if successful. - pub fn insert_char(&mut self, ch: char) -> bool { - if let Some(i) = self.char_index { - self.char_index = Some(i + 1); - // Unsafe because .text_mut() requires an update. - unsafe { - self.tree.text_mut().insert(i, ch); - } - // Here's the update. - self.update(); - return true; - } - false - } - - /// Delete the character before the selected position. - /// - /// This will fail if the cursor is in tree mode. - /// - /// Returns the deleted character. - pub fn delete_char(&mut self) -> Option { - if let Some(i) = self.char_index { - if i >= 1 { - self.char_index = Some(i - 1); - // Unsafe because .text_mut() requires an update. - let ch = unsafe { - self.tree.text_mut().remove(i - 1) - }; - // Here's the update. - self.update(); - return Some(ch) - } - } - None - } - - ///////////// - // PRIVATE // - ///////////// - - fn update(&mut self) { - self.tree.update(); - } -} - - -#[cfg(test)] -mod test { - use super::*; - use syntax::{TEST_TEXT, TEST_FOREST}; - use tree::Tree; - - fn t(children: Vec>) -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, children) - } - - fn bt(k: usize) -> Tree<'static> { - // What happens when you can make a doorway that - // connects the universe to a copy of itself, - // and then one of you makes n doorways? - t((0..k).map(|i| bt(i)).collect()) - } - - fn tt() -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "hi"), - Tree::new_text(&TEST_TEXT, "hey"))) - } - - #[test] - fn test_tree_navigation() { - let mut tree = bt(3); - let mut doc = Cursor::new(&mut tree); - - assert_eq!(doc.path(), vec!()); - // Down - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(0)); - // Down too far - assert_eq!(doc.down(), false); - assert_eq!(doc.path(), vec!(0)); - // Right - assert_eq!(doc.right(), true); - assert_eq!(doc.path(), vec!(1)); - // Right too far - assert_eq!(doc.right(), true); - assert_eq!(doc.right(), false); - assert_eq!(doc.path(), vec!(2)); - // Left - assert_eq!(doc.left(), true); - assert_eq!(doc.path(), vec!(1)); - // Left too far - assert_eq!(doc.left(), true); - assert_eq!(doc.left(), false); - assert_eq!(doc.path(), vec!(0)); - // Something to remember - assert_eq!(doc.right(), true); - assert_eq!(doc.right(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 0)); - assert_eq!(doc.right(), true); - assert_eq!(doc.path(), vec!(2, 1)); - // Up - assert_eq!(doc.up(), true); - assert_eq!(doc.path(), vec!(2)); - assert_eq!(doc.up(), true); - assert_eq!(doc.path(), vec!()); - // Up too far - assert_eq!(doc.up(), false); - assert_eq!(doc.path(), vec!()); - // Direct memory - assert_eq!(doc.down(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 1)); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 1, 0)); - // Indirect memory - assert_eq!(doc.up(), true); - assert_eq!(doc.up(), true); - assert_eq!(doc.left(), true); - assert_eq!(doc.up(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(1, 0)); - assert_eq!(doc.up(), true); - assert_eq!(doc.right(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 1, 0)); - } - - #[test] - fn test_text_navigation() { - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - - // Cannot enter text - assert_eq!(doc.mode(), TreeMode); - assert_eq!(doc.enter_text(), false); - assert_eq!(doc.mode(), TreeMode); - assert_eq!(doc.char_index(), None); - - // Can enter text - assert_eq!(doc.down(), true); - assert_eq!(doc.right(), true); - assert_eq!(doc.char_index(), None); - assert_eq!(doc.enter_text(), true); - assert_eq!(doc.mode(), TextMode); - assert_eq!(doc.char_index(), Some(3)); - - // Can navigate - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), false); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.right_char(), false); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.char_index(), Some(1)); - - // Can exit text - assert_eq!(doc.exit_text(), true); - assert_eq!(doc.mode(), TreeMode); - assert_eq!(doc.char_index(), None); - - // Character position is remembered - assert_eq!(doc.enter_text(), true); - assert_eq!(doc.char_index(), Some(1)); - - // Character position is not infectious - assert_eq!(doc.exit_text(), true); - assert_eq!(doc.left(), true); - assert_eq!(doc.enter_text(), true); - assert_eq!(doc.char_index(), Some(2)); - } - - #[test] - fn test_text_ops() { - // Tests for: insert char, delete char - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - - doc.down(); - doc.right(); - doc.enter_text(); - assert_eq!(doc.delete_char(), Some('y')); - doc.left_char(); - doc.left_char(); - assert_eq!(doc.delete_char(), None); - assert_eq!(doc.insert_char('l'), true); - doc.right_char(); - doc.right_char(); - assert_eq!(doc.insert_char('r'), true); - doc.exit_text(); - assert_eq!(doc.as_ref().text(), "lher"); - } - - #[test] - fn test_tree_ops() { - // Tests for: add child, replace tree, delete tree - let mut tree = bt(3); - let mut doc = Cursor::new(&mut tree); - - assert_eq!(doc.add_child(), true); - doc.down(); - doc.right(); - doc.right(); - doc.right(); - assert_eq!(doc.as_ref().len(), 0); - doc.left(); - let opt_tree = doc.delete_tree(); - assert!(opt_tree.is_some()); - assert_eq!(opt_tree.unwrap().len(), 2); - assert_eq!(doc.as_ref().len(), 0); - doc.left(); - let opt_tree = doc.replace_tree(&TEST_TEXT); - assert!(opt_tree.is_some()); - assert_eq!(opt_tree.unwrap().len(), 1); - assert_eq!(&doc.as_ref().node().construct().name, "TEST_TEXT"); - - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - doc.down(); - assert_eq!(doc.add_child(), false); - } - - #[test] - fn test_as_ref() { - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - - doc.down(); - doc.right(); - assert_eq!(doc.root().path(), &vec!()); - assert_eq!(doc.as_ref().path(), &vec!(1)); - } -} diff --git a/experiment/tree/mod.rs b/experiment/tree/mod.rs deleted file mode 100644 index 8a3614e..0000000 --- a/experiment/tree/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Synless is a tree editor. Here are the trees. -mod path; -mod tree; -mod tree_ref; -mod tree_mut; -mod cursor; - -pub use tree::tree_ref::TreeRef; -pub use tree::tree_mut::TreeMut; -pub use tree::tree::{Tree}; -pub use tree::path::{Path, extend_path, pop_path, match_end_of_path}; -pub use tree::cursor::{Cursor, Mode}; diff --git a/experiment/tree/path.rs b/experiment/tree/path.rs deleted file mode 100644 index 4ffbb34..0000000 --- a/experiment/tree/path.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Fun idea: implement dense Paths: -// lookup x on t: -// if x = 0 { -// t -// } -// else { -// let x = x - 1; -// let n = t.num_children(); -// let (child, n) = (x % n, x / n); -// lookup x on t[child] -// (For more efficiency, align to power of 2.) - -/// A position in the document. -pub type Path = Vec; - -/// Construct a new path that points to the `extension`'th child of -/// `path`. -pub fn extend_path(path: &Path, extension: usize) -> Path { - let mut path = path.clone(); - path.push(extension); - path -} - -/// Construct a new path that points to the parent of `path`. -/// If `path` is to the root and has no parent, then return it -/// unchanged. -pub fn pop_path(path: &Path) -> Path { - let mut path = path.clone(); - path.pop(); - path -} - - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_paths() { - let path = vec!(0, 2, 1); - assert_eq!(extend_path(&path, 3), vec!(0, 2, 1, 3)); - assert_eq!(path, vec!(0, 2, 1)); - assert_eq!(pop_path(&path), vec!(0, 2)); - assert_eq!(path, vec!(0, 2, 1)); - } -} diff --git a/experiment/tree/tree.rs b/experiment/tree/tree.rs deleted file mode 100644 index 7a75a17..0000000 --- a/experiment/tree/tree.rs +++ /dev/null @@ -1,335 +0,0 @@ -use std::ops::{Index, IndexMut}; - -use tree::path::Path; - -use self::Children::{ForestChildren, TextChildren}; - - -/// A document being edited, represented as a Tree. -/// Because how else would you represent it? -/// -/// Every tree is either *foresty*, if it has trees for children, -/// or *texty*, if it contains text. -#[derive(Debug)] -pub struct Tree<'l> { - pub node: Node<'l>, - children: Children<'l> -} - -#[derive(Debug)] -enum Children<'l> { - ForestChildren(Vec>), - TextChildren(String) // TODO: Allow Tree children interspersed -} - - -// Children // - -impl<'l> Children<'l> { - - // TODO: efficiency - // Returns apparent length: text has a phantom character at the end - fn len(&self) -> usize { - match self { - &ForestChildren(ref trees) => trees.len(), - &TextChildren(ref childs) => childs.chars().count() + 1 - } - } - - fn is_texty(&self) -> bool { - match self { - &ForestChildren(_) => false, - &TextChildren(_) => true - } - } - - fn is_foresty(&self) -> bool { - match self { - &ForestChildren(_) => true, - &TextChildren(_) => false - } - } -} - - -// Trees // - -impl<'l> Tree<'l> { - - // Tree Constructors - - /// Construct a new hole. - pub fn new_hole() -> Tree<'l> { - Tree{ - node: Node::new_hole(), - children: ForestChildren(vec!()) - } - } - - /// Construct a tree with empty children/text. - pub fn new(construct: &'l Construct) -> Tree<'l> { - if construct.arity.is_text() { - Tree::new_text(construct, "") - } else { - let arity = construct.arity.arity(); - let mut children = vec!(); - for _ in 0..arity { - children.push(Tree::new_hole()); - } - Tree::new_forest(construct, children) - } - } - - /// Construct a new foresty tree. - pub fn new_forest(construct: &'l Construct, children: Vec>) -> Tree<'l> { - let node = { - let child_nodes = children.iter().map(|t| &t.node).collect(); - Node::new_forest(construct, child_nodes) - }; - Tree{ - node: node, - children: ForestChildren(children) - } - } - - /// Construct a new texty tree. - pub fn new_text(construct: &'l Construct, text: &str) -> Tree<'l> { - let node = Node::new_text(construct, text); - Tree{ - node: node, - children: TextChildren(text.to_string()) - } - } - - /// *This must be called every time this tree or one of its - /// children is modifed, on this tree and every tree of to the root.* - pub fn local_update(&mut self) { - match &self.children { - &ForestChildren(ref forest) => { - let child_nodes = forest.into_iter().map(|n| &n.node).collect(); - self.node.update_forest(child_nodes); - } - &TextChildren(ref text) => self.node.update_text(text) - } - } - - // All Trees // - - /// The number of children of a foresty tree, - /// or the number of characters plus one of a texty tree. - /// (It is plus one because there is a phantom character at the - /// end of text to make it easier to edit.) - pub fn len(&self) -> usize { - self.children.len() - } - - // Texty Trees // - - /// Is this tree texty? - pub fn is_texty(&self) -> bool { - self.children.is_texty() - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text(&self) -> &String { - match &self.children { - &ForestChildren(_) => error_not_texty(), - &TextChildren(ref text) => text - } - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text_mut(&mut self) -> &mut String { - match &mut self.children { - &mut ForestChildren(_) => error_not_texty(), - &mut TextChildren(ref mut text) => text - } - } - - // Foresty Trees // - - /// Is this tree foresty? - pub fn is_foresty(&self) -> bool { - self.children.is_foresty() - } - - /// Return the children of a foresty tree. Panics on texty trees. - pub fn forest(&self) -> &Vec> { - match &self.children { - &ForestChildren(ref forest) => forest, - &TextChildren(_) => error_not_foresty() - } - } - - /// Return the children of a foresty tree. Panics on texty trees. - pub fn forest_mut(&mut self) -> &mut Vec> { - match &mut self.children { - &mut ForestChildren(ref mut forest) => forest, - &mut TextChildren(_) => error_not_foresty() - } - } -} - - -// Index // - -impl<'l> Index for Children<'l> { - type Output = Tree<'l>; - fn index(&self, index: usize) -> &Tree<'l> { - match self { - &ForestChildren(ref forest) => { - match forest.get(index) { - None => error_invalid_path(), - Some(tree) => tree - } - } - &TextChildren(_) => error_invalid_path() - } - } -} - -impl<'l> Index for Tree<'l> { - /// - type Output = Tree<'l>; - /// Get the i'th child, or error. - fn index(&self, i: usize) -> &Tree<'l> { - self.children.index(i) - } -} - -impl<'a, 'l> Index<&'a [usize]> for Tree<'l> { - /// - type Output = Tree<'l>; - /// Lookup the path (represented as a slice), or error. - fn index(&self, path: &[usize]) -> &Tree<'l> { - match path.split_first() { - None => self, - Some((&i, path)) => self[i].index(path) - } - } -} - -impl<'a, 'l> Index<&'a Path> for Tree<'l> { - /// - type Output = Tree<'l>; - /// Lookup the path, or error. - fn index(&self, path: &Path) -> &Tree<'l> { - self.index(path.as_slice()) - } -} - - -// Index Mut // - -impl<'l> IndexMut for Children<'l> { - fn index_mut(&mut self, i: usize) -> &mut Tree<'l> { - match self { - &mut ForestChildren(ref mut forest) => { - match forest.get_mut(i) { - None => error_invalid_path(), - Some(tree) => tree - } - } - &mut TextChildren(_) => error_invalid_path() - } - } -} - -impl<'l> IndexMut for Tree<'l> { - /// Get a mutable reference to the `i`th child, or error. - fn index_mut(&mut self, index: usize) -> &mut Tree<'l> { - self.children.index_mut(index) - } -} - -impl<'a, 'l> IndexMut<&'a [usize]> for Tree<'l> { - /// Get a mutable reference to the path (represented as a slice), - /// or error. - fn index_mut(&mut self, path: &[usize]) -> &mut Tree<'l> { - match path.split_first() { - None => self, - Some((&i, path)) => self[i].index_mut(path) - } - } -} - -impl<'a, 'l> IndexMut<&'a Path> for Tree<'l> { - /// Get a mutable reference to the path, or error. - fn index_mut(&mut self, path: &Path) -> &mut Tree<'l> { - self.index_mut(path.as_slice()) - } -} - - -// Errors // - -fn error_invalid_path() -> ! { - panic!("tree - encountered invalid path!"); -} - -fn error_not_texty() -> ! { - panic!("tree - expected a texty tree!"); -} - -fn error_not_foresty() -> ! { - panic!("tree - expected a foresty tree!"); -} - -#[cfg(test)] -pub fn example_tree() -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "hi"), - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "world"))))) -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_trees() { - let mut tree = example_tree(); - assert_eq!(tree.len(), 2); - assert_eq!(tree[0].len(), 3); - assert_eq!(tree[1].len(), 1); - assert_eq!(tree[&vec!()].len(), 2); - assert_eq!(tree[&vec!(0)].len(), 3); - assert_eq!(tree[&vec!(1)].len(), 1); - assert_eq!(tree[&vec!(1, 0)].len(), 6); - - { - let root = &tree; - let hi = &tree[0]; - - assert!(root.is_foresty()); - assert!(!root.is_texty()); - assert!(!hi.is_foresty()); - assert!(hi.is_texty()); - - assert_eq!(hi.text().as_str(), "hi"); - assert_eq!(root.forest().len(), 2); - } - { - let world = &mut tree[1][0]; - assert_eq!(world.text_mut().as_str(), "world"); - } - { - let sub = &mut tree[1]; - assert_eq!(sub.forest_mut().len(), 1); - } - } - - #[test] - #[should_panic(expected = "expected a texty tree")] - fn test_text_panic() { - example_tree().text(); - } - - #[test] - #[should_panic(expected = "expected a foresty tree")] - fn test_forest_panic() { - example_tree()[0].forest(); - } -} diff --git a/experiment/tree/tree_mut.rs b/experiment/tree/tree_mut.rs deleted file mode 100644 index 8960786..0000000 --- a/experiment/tree/tree_mut.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::mem; - -use tree::path::Path; -use tree::node::Node; -use tree::tree::Tree; -use tree::tree_ref::TreeRef; - - -/// A mutable reference to a subtree of a containing tree. -/// Obtain one by calling `.as_mut()` on a `Tree`. -/// -/// The containing tree is called the root. -/// Calling `.goto_child(i)` or `.goto_parent()` will change the -/// subtree, but leave the root the same. -/// -/// Notice that, unlike for TreeRefs, navigation operations consume -/// the TreeMut. -pub struct TreeMut<'t, 'l : 't> { - root: &'t mut Tree<'l>, - path: Path -} - -impl<'t, 'l> Tree<'l> { - pub(crate) fn as_mut(&'t mut self) -> TreeMut<'t, 'l> { - TreeMut{ - root: self, - path: vec!() - } - } -} - -impl<'t, 'l> TreeMut<'t, 'l> { - fn tree(&self) -> &Tree<'l> { - &self.root[&self.path] - } - - fn tree_parent(&self) -> &Tree<'l> { - &self.root[&self.path[0..self.path.len()-1]] - } - - fn tree_mut(&mut self) -> &mut Tree<'l> { - &mut self.root[&self.path] - } - - fn ancestor_mut(&mut self, gen: usize) -> &mut Tree<'l> { - // gen is the number of generations *back* to go. - &mut self.root[&self.path[0 .. self.path.len() - gen]] - } - - // All Trees // - - /// *This must be called every time this tree or one of its - /// children is modifed.* - pub(in tree) fn update(&mut self) { - let n = self.path.len(); - for gen in 0 .. n + 1 { - self.ancestor_mut(gen).local_update(); - } - } - - pub(in tree) fn as_ref<'t2>(&'t2 self) -> TreeRef<'t2, 'l> { - TreeRef::new(&self.root, self.path.clone()) - } - - /// Replace this tree in-place. Returns the old tree. - pub(in tree) fn replace(&mut self, tree: Tree<'l>) -> Tree<'l> { - mem::replace(&mut self.tree_mut(), tree) - } - - // Local Info // - - pub(in tree) fn node(&self) -> &Node { - &self.tree().node - } - - pub(in tree) fn breadcrumb(&self) -> usize { - self.tree().node.breadcrumb - } - - pub(in tree) fn breadcrumb_mut(&mut self) -> &mut usize { - &mut self.tree_mut().node.breadcrumb - } - -/* pub(in tree) fn len(&self) -> usize { - self.tree().len() - }*/ - - pub(in tree) fn path(&self) -> &Path { // for testing - &self.path - } - - // Texty Trees // - - pub(in tree) fn is_texty(&self) -> bool { - self.tree().is_texty() - } - - pub(in tree) fn text(&self) -> &String { - self.tree().text() - } - - /// DANGER: If the text is modified, its parent's node must be updated! - pub(in tree) unsafe fn text_mut(&mut self) -> &mut String { - self.tree_mut().text_mut() - } - - // Foresty Trees // - - pub(in tree) fn is_foresty(&self) -> bool { - self.tree().is_foresty() - } -/* - pub(in tree) fn forest(&self) -> &Vec> { - self.tree().forest() - } -*/ - /// DANGER: If the forest is modified, its parent's node must be updated! - pub(in tree) unsafe fn forest_mut(&mut self) -> &mut Vec> { - self.tree_mut().forest_mut() - } - - pub(in tree) fn num_children(&self) -> usize { - self.tree().forest().len() - } - - pub(in tree) fn goto_child(&mut self, i: usize) { - self.path.push(i); - } - - // Trees with Parents // - - pub(in tree) fn has_parent(&self) -> bool { - !self.path.is_empty() - } - - pub(in tree) fn index(&self) -> usize { - *self.path.last().expect("tree_mut: index") - } - - pub(in tree) fn num_siblings(&self) -> usize { - self.tree_parent().forest().len() - } - - pub(in tree) fn goto_parent(&mut self) -> usize { - self.path.pop().unwrap() - } -} - - -#[cfg(test)] -mod tests { - use tree::tree::example_tree; - - #[test] - fn test_tree_mut_goto() { - let mut tree = example_tree(); - let mut r = tree.as_mut(); - r.goto_child(1); - r.goto_parent(); - r.goto_child(1); - r.goto_child(0); - r.goto_parent(); - assert_eq!(r.as_ref().root().len(), 2); - assert_eq!(r.len(), 1); - } -} diff --git a/experiment/tree/tree_ref.rs b/experiment/tree/tree_ref.rs deleted file mode 100644 index d762af6..0000000 --- a/experiment/tree/tree_ref.rs +++ /dev/null @@ -1,127 +0,0 @@ -use coord::*; -use syntax::{BoundSet, LayoutRegion}; -use tree::path::{Path, extend_path}; -use tree::node::Node; -use tree::tree::Tree; - -/// A reference to a subtree of a containing tree. -/// Obtain one by calling `.as_ref()` on a `Tree`. -/// -/// The containing tree is called the root. -/// Calling `.child(i)` will change the subtree, but leave the root -/// the same. -#[derive(Clone)] -pub struct TreeRef<'t, 'l : 't> { - root: &'t Tree<'l>, - path: Path -} - -impl<'t, 'l> Tree<'l> { - /// Obtain a TreeRef, whose root and subtree are both this tree. - pub fn as_ref(&'t self) -> TreeRef<'t, 'l> { - TreeRef{ - root: &self, - path: vec!() - } - } -} - -impl<'t, 'l> TreeRef<'t, 'l> { - - pub(in tree) fn new(root: &'t Tree<'l>, path: Path) -> TreeRef<'t, 'l> { - TreeRef{ - root: root, - path: path - } - } - - fn tree(&self) -> &Tree<'l> { - &self.root[&self.path] - } - - /// Lay out the document to fit within the Bound. - pub fn lay_out(&self, region: Region) -> LayoutRegion { - let child_bounds = if self.is_foresty() { - let n = self.len(); - (0..n).map(|i| self.child(i).node().bounds.clone()).collect() - } else { - vec!(BoundSet::literal(self.text())) - }; - let empty_text = self.is_texty() && self.text().is_empty(); - let syn = &self.node().construct.syntax; - let arity = self.node().construct.arity.arity(); - let layouts = syn.lay_out(arity, &child_bounds, empty_text); - let region = Region{ - pos: region.pos, - bound: self.node().bounds.fit_bound(region.bound) - }; - layouts.pick(region.bound).regionize(region) - } - - /// The root (the containing tree). - pub fn root(&self) -> TreeRef<'t, 'l> { - TreeRef{ - root: self.root, - path: vec!() - } - } - - /// The path from the root to this tree. - pub fn path(&self) -> &Path { - &self.path - } - - // All Trees // - - /// This tree's node. - pub fn node(&self) -> &Node<'l> { - &self.tree().node - } - - /// The number of children of a foresty tree, - /// or the number of characters plus one of a texty tree. - pub fn len(&self) -> usize { - self.tree().len() - } - - // Texty Trees // - - /// Is this tree texty? - pub fn is_texty(&self) -> bool { - self.tree().is_texty() - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text(&self) -> &String { - self.tree().text() - } - - // Foresty Trees // - - /// Is this tree foresty? - pub fn is_foresty(&self) -> bool { - self.tree().is_foresty() - } - - /// Get the `i`th child. Mustn't be called on a texty tree. - pub fn child(&self, i: usize) -> TreeRef<'t, 'l> { - TreeRef{ - root: &self.root, - path: extend_path(&self.path, i) - } - } -} - - -#[cfg(test)] -mod tests { - use tree::tree::example_tree; - - #[test] - fn test_tree_ref_child() { - let tree = example_tree(); - let r = tree.as_ref().child(1).child(0); - assert_eq!(r.root().len(), 2); - assert_eq!(r.len(), 6); - } -} diff --git a/forest/Cargo.toml b/forest/Cargo.toml deleted file mode 100644 index 0089165..0000000 --- a/forest/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -workspace = ".." -name = "forest" -version = "0.1.0" -authors = ["Justin Pombrio "] -edition = "2018" - -[dependencies] -generational-arena = "0.2" diff --git a/forest/src/lib.rs b/forest/src/lib.rs deleted file mode 100644 index 36bab29..0000000 --- a/forest/src/lib.rs +++ /dev/null @@ -1,631 +0,0 @@ -//! A collection of trees. -//! -//! There are two kinds of tree nodes: _branch_ nodes and _leaf_ nodes. -//! Both kinds of nodes have data D. In addition: -//! -//! - Branch nodes have zero or more children -//! - Leaf nodes have _additional_ leaf data L -//! -//! **Every method on `Node` may panic, if that node was deleted.** -//! Deleting the ancestor of a node will delete the node. The one exception -//! is the `is_valid()` method, which checks whether a node has been deleted. -//! -//! This library solves these problems: -//! -//! - Ensuring that parent and child links always agree. -//! - Ensuring that every tree is accounted for. -//! - Preventing cycles at runtime. -//! -//! It does NOT solve these problems: -//! -//! - Preventing "use after free" (see the note on deletion above). -//! Along the same lines, preventing cycles at compile time. -//! - Removing the need to pass the `Forest` in to every method call. - -// Note to self: solving either of the "does not solve" problems will ~double -// the size of this library, and thereby also increase the size of its -// caller because the caller will need to wrap everything. It's not worth it. -// Let the caller deal with it. - -// INVARIANTS: -// - n.parent is None iff n in Forest.roots -// - n.parent is Some(p) iff arena[p] is a branch node containing n -// - Every node is a descandant of one of the roots - -use generational_arena::{Arena, Index}; -use std::iter; -use std::marker::PhantomData; - -#[derive(Debug)] -pub struct Forest { - roots: Vec>, - arena: Arena>, -} - -#[derive(Debug)] -pub struct Node(Index, PhantomData<(D, L)>); - -#[derive(Debug)] -struct NodeContents { - parent: Option>, - data: D, - leaf_or_children: LeafOrChildren, -} - -#[derive(Debug)] -enum LeafOrChildren { - Leaf(L), - Children(Vec>), -} - -use LeafOrChildren::{Children, Leaf}; - -impl Default for Forest { - fn default() -> Forest { - Forest::new() - } -} - -impl PartialEq for Node { - fn eq(&self, other: &Node) -> bool { - self.0 == other.0 - } -} - -impl Eq for Node {} - -impl Clone for Node { - fn clone(&self) -> Node { - Node(self.0, self.1) - } -} - -impl Copy for Node {} - -impl Forest { - /// Construct a new forest. - pub fn new() -> Forest { - Forest { - roots: Vec::new(), - arena: Arena::new(), - } - } - - /// Construct a new leaf. - pub fn new_leaf(&mut self, data: D, leaf: L) -> Node { - let node = Node::new(self.arena.insert(NodeContents { - parent: None, - data, - leaf_or_children: Leaf(leaf), - })); - self.roots.push(node); - node - } - - /// Construct a new branch with no children. - pub fn new_branch(&mut self, data: D) -> Node { - let node = Node::new(self.arena.insert(NodeContents { - parent: None, - data, - leaf_or_children: Children(Vec::new()), - })); - self.roots.push(node); - node - } - - /// Iterate over all nodes. This is an ExactSizeIterator, so you can call `.len()`. - pub fn iter_nodes(&self) -> impl ExactSizeIterator> + '_ { - self.arena.iter().map(|(id, _)| Node::new(id)) - } - - /// All trees. - pub fn roots(&self) -> &[Node] { - &self.roots - } - - fn get(&self, node: Node) -> &NodeContents { - match self.arena.get(node.0) { - None => panic!("Forest - node has been deleted!"), - Some(node) => node, - } - } - - fn get_mut(&mut self, node: Node) -> &mut NodeContents { - match self.arena.get_mut(node.0) { - None => panic!("Forest - node has been deleted (mut)!"), - Some(node) => node, - } - } - - fn leaf(&self, node: Node) -> &L { - match &self.get(node).leaf_or_children { - Leaf(leaf) => leaf, - Children(_) => panic!("Forest - branch nodes do not have leaf data"), - } - } - - fn leaf_mut(&mut self, node: Node) -> &mut L { - match &mut self.get_mut(node).leaf_or_children { - Leaf(leaf) => leaf, - Children(_) => panic!("Forest - branch nodes do not have leaf data (mut)"), - } - } - - fn children(&self, node: Node) -> &[Node] { - match &self.get(node).leaf_or_children { - Leaf(_) => panic!("Forest - leaf nodes do not have children"), - Children(children) => children, - } - } - - fn children_mut(&mut self, node: Node) -> &mut Vec> { - match &mut self.get_mut(node).leaf_or_children { - Leaf(_) => panic!("Forest - leaf nodes do not have children (mut)"), - Children(children) => children, - } - } - - fn siblings(&self, node: Node) -> &[Node] { - let parent = self.get(node).parent; - match parent { - None => &self.roots, - Some(parent) => self.children(parent), - } - } - - fn siblings_mut(&mut self, node: Node) -> &mut Vec> { - let parent = self.get(node).parent; - match parent { - None => &mut self.roots, - Some(parent) => self.children_mut(parent), - } - } - - fn sibling_index(&self, node: Node) -> usize { - self.siblings(node) - .iter() - .position(|t| *t == node) - .expect("Forest - missing child") - } - - // Check if either tree is a descendant of the other - fn overlaps(&self, node_1: Node, node_2: Node) -> bool { - node_1.ancestors(self).any(|n| n == node_2) || node_2.ancestors(self).any(|n| n == node_1) - } -} - -struct AncestorIter<'f, D, L> { - forest: &'f Forest, - node: Option>, -} - -impl<'f, D, L> iter::Iterator for AncestorIter<'f, D, L> { - type Item = Node; - - fn next(&mut self) -> Option> { - if let Some(node) = self.node { - self.node = node.parent(self.forest); - Some(node) - } else { - None - } - } -} - -impl Node { - fn new(index: Index) -> Node { - Node(index, PhantomData) - } - - /// Check if this node is still valid (has not been deleted). - pub fn is_valid(self, f: &Forest) -> bool { - f.arena.contains(self.0) - } - - /// Get this node's parent. Returns `None` if already at the root. - pub fn parent(self, f: &Forest) -> Option> { - f.get(self).parent - } - - /// Iterate over all ancestors (recursive parents) of this node, - /// starting with itself and going to the root. - pub fn ancestors(self, f: &Forest) -> impl Iterator> + '_ { - AncestorIter { - forest: f, - node: Some(self), - } - } - - /// Get the root of the tree containing this node. (This is the same as - /// calling `parent()` repeatedly.) - pub fn root(self, f: &Forest) -> Node { - let mut root = self; - while let Some(parent) = root.parent(f) { - root = parent; - } - root - } - - /// Get the data for this node. - pub fn data(self, f: &Forest) -> &D { - &f.get(self).data - } - - /// Mutably get the data for this node. - pub fn data_mut(self, f: &mut Forest) -> &mut D { - &mut f.get_mut(self).data - } - - /// Return `true` if this is a leaf node (containing `L`), or `false` if it's - /// a branch node (containing children). - pub fn is_leaf(self, f: &Forest) -> bool { - matches!(f.get(self).leaf_or_children, Leaf(_)) - } - - /// Get the leaf data at this leaf node. - /// - /// # Panics - /// - /// Panics if this node is not a leaf. - pub fn leaf(self, f: &Forest) -> &L { - f.leaf(self) - } - - /// Mutably get the leaf data at this leaf node. - /// - /// # Panics - /// - /// Panics if this node is not a leaf. - pub fn leaf_mut(self, f: &mut Forest) -> &mut L { - f.leaf_mut(self) - } - - /// Get this node's children. - /// - /// # Panics - /// - /// Panics if this is not a branch node. - pub fn children(self, f: &Forest) -> &[Node] { - f.children(self) - } - - /// This node's siblings, in order, including itself. - /// If this is a root node, its siblings are the roots. - pub fn siblings(self, f: &Forest) -> &[Node] { - f.siblings(self) - } - - /// Determine this node’s index among its siblings. - /// If this is a root node, its siblings are the roots. - pub fn sibling_index(self, f: &Forest) -> usize { - f.sibling_index(self) - } - - /// Detach this node from its parent. Afterwards, it will be a root node, - /// and its parent will have one fewer child. If this node was already a - /// root node, this is a no-op. - pub fn detach(self, f: &mut Forest) { - if let Some(parent) = f.get(self).parent { - let i = f.sibling_index(self); - f.children_mut(parent).remove(i); - f.get_mut(self).parent = None; - f.roots.push(self); - } - } - - /// Insert `node` as this node's i'th child. `node` must be a root node; - /// if it might not be you must detach() it first. - /// - /// # Panics - /// - /// Panics if `self` isn't a branch, or if `child_index` is out of bounds, - /// or if `node` isn't a root, or if you attempt to create a cycle. - pub fn insert_child(self, f: &mut Forest, child_index: usize, node: Node) { - if node.parent(f).is_some() { - // This eliminates the tricky case of moving list element #2 to position #4! - panic!("Forest - insert_child can only insert a root node"); - } - if self.root(f) == node { - panic!("Forest - attempt to create cycle using `insert_child` thwarted"); - } - let i = f.sibling_index(node); - f.siblings_mut(node).remove(i); - f.get_mut(node).parent = Some(self); - f.children_mut(self).insert(child_index, node); - } - - /// Swap the locations of nodes `self` and `other`. - /// - /// # Panics - /// - /// Panics if either node contains (is an ancestor of) the other. - pub fn swap(self, f: &mut Forest, other: Node) { - if f.overlaps(self, other) { - panic!("Forest - swap can only be called on non-overlapping nodes"); - } - let i = f.sibling_index(self); - let j = f.sibling_index(other); - let self_parent = f.get(self).parent; - let other_parent = f.get(other).parent; - f.siblings_mut(self)[i] = other; - f.siblings_mut(other)[j] = self; - f.get_mut(self).parent = other_parent; - f.get_mut(other).parent = self_parent; - } - - /// Remove this node from its parent (if any), and delete it - /// and all of its descendants. - pub fn delete(self, f: &mut Forest) { - self.detach(f); - f.roots.retain(|r| *r != self); - let mut to_delete = vec![self]; - while let Some(node) = to_delete.pop() { - let contents = &mut f.get_mut(node); - if let Children(children) = &mut contents.leaf_or_children { - to_delete.append(children); - } - f.arena.remove(node.0); - } - } -} - -#[cfg(test)] -mod forest_tests { - use super::*; - use std::fmt::{Debug, Display}; - - /// Verify and print a forest. Panic if verification fails. Verification checks: - /// - Every node is accounted for in a tree in `roots` - /// - node.parent is None if it's a root, or Some(parent) if it's a child - struct Verifier<'a, D: Debug + Display, L: Debug + Display> { - node_count: usize, - display: String, - forest: &'a Forest, - } - - impl<'a, D: Debug + Display, L: Debug + Display> Verifier<'a, D, L> { - fn new(forest: &'a Forest) -> Verifier<'a, D, L> { - Verifier { - node_count: 0, - display: String::new(), - forest, - } - } - - fn verify(mut self) -> String { - // Walk each tree - for (i, root) in self.forest.roots().iter().copied().enumerate() { - self.verify_tree(root, Vec::new(), root); - assert_eq!(root.sibling_index(&self.forest), i); - assert_eq!(root.siblings(&self.forest), self.forest.roots()); - } - // Check that every node has been accounted for - assert_eq!(self.node_count, self.forest.iter_nodes().len()); - self.display - } - - fn verify_tree( - &mut self, - node: Node, - expected_ancestors: Vec>, - expected_root: Node, - ) { - assert_eq!( - node.parent(&self.forest), - expected_ancestors.first().copied() - ); - assert_eq!(node.root(&self.forest), expected_root); - assert!(node.is_valid(&self.forest)); - self.display.push('('); - self.display - .push_str(&format!("{}", node.data(&self.forest))); - self.node_count += 1; - if node.is_leaf(&self.forest) { - self.display - .push_str(&format!(" {}", node.leaf(&self.forest))); - } else { - for (i, child) in node.children(&self.forest).iter().copied().enumerate() { - self.display.push(' '); - let mut new_ancestors = vec![node]; - new_ancestors.extend_from_slice(&expected_ancestors); - self.verify_tree(child, new_ancestors, expected_root); - assert_eq!(child.sibling_index(&self.forest), i); - assert_eq!(child.siblings(&self.forest), node.children(&self.forest)); - } - } - self.display.push(')'); - } - } - - fn verify_and_print(forest: &Forest) -> String { - Verifier::new(forest).verify() - } - - fn make_mirror(forest: &mut Forest, height: u32, id: u32) -> Node { - if height == 0 { - forest.new_leaf(id, 'a') - } else { - let parent = forest.new_branch(id); - for i in 0..height { - let child = make_mirror(forest, i, id + 2_u32.pow(i)); - parent.insert_child(forest, i as usize, child); - } - parent - } - } - - #[test] - fn test_leaf() { - let mut forest = Forest::new(); - forest.new_leaf("data", "leaf"); - assert_eq!(verify_and_print(&forest), "(data leaf)"); - } - - #[test] - fn test_branch() { - let mut f = Forest::new(); - let parent = f.new_branch("parent"); - let elder_sister = f.new_leaf("Sister", "elder"); - let younger_sister = f.new_leaf("sister", "younger"); - parent.insert_child(&mut f, 0, elder_sister); - parent.insert_child(&mut f, 1, younger_sister); - assert_eq!( - verify_and_print(&f), - "(parent (Sister elder) (sister younger))" - ); - } - - #[test] - fn test_mirror() { - let mut f = Forest::new(); - make_mirror(&mut f, 3, 0); - assert_eq!( - verify_and_print(&f), - "(0 (1 a) (2 (3 a)) (4 (5 a) (6 (7 a))))" - ); - } - - #[test] - fn test_mutation() { - let mut f = Forest::new(); - let root = make_mirror(&mut f, 3, 0); - *root.data_mut(&mut f) = 100; - *root.children(&f)[1].children(&f)[0].leaf_mut(&mut f) = 'b'; - let last_child = root.children(&f)[2]; - *last_child.children(&f)[0].leaf_mut(&mut f) = 'c'; - *last_child.children(&f)[1].children(&f)[0].leaf_mut(&mut f) = 'd'; - assert_eq!( - verify_and_print(&f), - "(100 (1 a) (2 (3 b)) (4 (5 c) (6 (7 d))))" - ); - } - - #[test] - fn test_modification() { - let mut f = Forest::<&'static str, u32>::new(); - let kid = f.new_branch("kid"); - let mama = f.new_branch("mama"); - kid.insert_child(&mut f, 0, mama); - let papa = f.new_branch("papa"); - kid.insert_child(&mut f, 1, papa); - let gram = f.new_leaf("gram", 99); - mama.insert_child(&mut f, 0, gram); - let gramp = f.new_leaf("gramp", 100); - mama.insert_child(&mut f, 0, gramp); - let ogram = f.new_leaf("ogram", 79); - papa.insert_child(&mut f, 0, ogram); - let ogramp = f.new_leaf("ogramp", 80); - papa.insert_child(&mut f, 0, ogramp); - assert_eq!( - verify_and_print(&f), - "(kid (mama (gramp 100) (gram 99)) (papa (ogramp 80) (ogram 79)))" - ); - - mama.detach(&mut f); - mama.detach(&mut f); - assert_eq!( - verify_and_print(&f), - "(kid (papa (ogramp 80) (ogram 79)))(mama (gramp 100) (gram 99))" - ); - - gramp.detach(&mut f); - kid.insert_child(&mut f, 0, gramp); - assert_eq!( - verify_and_print(&f), - "(kid (gramp 100) (papa (ogramp 80) (ogram 79)))(mama (gram 99))" - ); - - kid.swap(&mut f, mama); - gramp.swap(&mut f, gram); - assert_eq!( - verify_and_print(&f), - "(mama (gramp 100))(kid (gram 99) (papa (ogramp 80) (ogram 79)))" - ); - - papa.delete(&mut f); - assert!(!papa.is_valid(&f)); - assert!(!ogramp.is_valid(&f)); - assert!(!ogram.is_valid(&f)); - assert_eq!(verify_and_print(&f), "(mama (gramp 100))(kid (gram 99))"); - } - - // Error Testing // - - #[test] - #[should_panic(expected = "Forest - leaf nodes do not have children")] - fn test_children_panic() { - let mut f = Forest::<(), ()>::new(); - let tree = f.new_leaf((), ()); - tree.children(&f); - } - - #[test] - #[should_panic(expected = "Forest - branch nodes do not have leaf data")] - fn test_leaf_panic() { - let mut f = Forest::<(), ()>::new(); - let tree = f.new_branch(()); - tree.leaf(&f); - } - - #[test] - #[should_panic(expected = "Forest - branch nodes do not have leaf data (mut)")] - fn test_leaf_mut_panic() { - let mut f = Forest::<(), ()>::new(); - let tree = f.new_branch(()); - tree.leaf_mut(&mut f); - } - - #[test] - #[should_panic(expected = "insertion index")] - fn test_insert_oob_panic() { - let mut f = Forest::<(), ()>::new(); - let tree = f.new_branch(()); - let child = f.new_leaf((), ()); - tree.insert_child(&mut f, 1, child); - } - - #[test] - #[should_panic(expected = "Forest - leaf nodes do not have children")] - fn test_insert_leaf_panic() { - let mut f = Forest::<(), ()>::new(); - let leaf = f.new_leaf((), ()); - let child = f.new_leaf((), ()); - leaf.insert_child(&mut f, 0, child); - } - - #[test] - #[should_panic(expected = "Forest - insert_child can only insert a root node")] - fn test_insert_non_root_panic() { - let mut f = Forest::<(), ()>::new(); - let parent = f.new_branch(()); - let child = f.new_leaf((), ()); - parent.insert_child(&mut f, 0, child); - let parent_2 = f.new_branch(()); - parent_2.insert_child(&mut f, 0, child); - } - - #[test] - #[should_panic(expected = "Forest - attempt to create cycle using `insert_child` thwarted")] - fn test_cycle() { - let mut f = Forest::::new(); - let tree = f.new_branch(0); - tree.insert_child(&mut f, 0, tree); - } - - #[test] - #[should_panic(expected = "Forest - swap can only be called on non-overlapping nodes")] - fn test_swap_cycle() { - let mut f = Forest::::new(); - let tree = f.new_branch(0); - tree.swap(&mut f, tree); - } - - #[test] - #[should_panic(expected = "Forest - node has been deleted!")] - fn test_use_after_free_panic() { - let mut f = Forest::::new(); - let tree = f.new_branch(0); - tree.delete(&mut f); - tree.data(&f); - } -} diff --git a/frontends/Cargo.toml b/frontends/Cargo.toml deleted file mode 100644 index 7dcbb04..0000000 --- a/frontends/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -workspace = ".." -name = "frontends" -version = "0.1.0" -authors = ["Justin Pombrio ", "e-matteson "] -edition = "2018" - -[dependencies] -partial-pretty-printer = { git = "https://github.com/justinpombrio/partial-pretty-printer" } -termion = "1.5" -thiserror = "1.0" - diff --git a/frontends/src/color_theme.rs b/frontends/src/color_theme.rs deleted file mode 100644 index 9171e2d..0000000 --- a/frontends/src/color_theme.rs +++ /dev/null @@ -1,141 +0,0 @@ -use partial_pretty_printer::{Color, Shade, ShadedStyle}; - -/// A color theme. -/// -/// The colors are nominally the six standard terminal colors (plus -/// white), but just like terminal colors they don't actually need to -/// match their name. (For example, all colors could be shades of -/// green or blue.) -/// -/// The shades are used to shade the background of ancestors of the -/// selected node (by default in dark gray). `shade0` is the strongest -/// (i.e., lightest) shade, and `shade3` is the weakest (i.e., -/// darkest) shade, which is used for most of the background. -/// -/// `cursor` is the color of the cursor. -#[allow(non_snake_case)] -pub struct ColorTheme { - /// Default Background - pub base00: Rgb, - /// Lighter Background (Used for status bars) - pub base01: Rgb, - /// Selection Background - pub base02: Rgb, - /// Comments, Invisibles, Line Highlighting - pub base03: Rgb, - /// Dark Foreground (Used for status bars) - pub base04: Rgb, - /// Default Foreground, Caret, Delimiters, Operators - pub base05: Rgb, - /// Light Foreground (Not often used) - pub base06: Rgb, - /// Light Background (Not often used) - pub base07: Rgb, - /// Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted - pub base08: Rgb, - /// Integers, Boolean, Constants, XML Attributes, Markup Link Url - pub base09: Rgb, - /// Classes, Markup Bold, Search Text Background - pub base0A: Rgb, - /// Strings, Inherited Class, Markup Code, Diff Inserted - pub base0B: Rgb, - /// Support, Regular Expressions, Escape Characters, Markup Quotes - pub base0C: Rgb, - /// Functions, Methods, Attribute IDs, Headings - pub base0D: Rgb, - /// Keywords, Storage, Selector, Markup Italic, Diff Changed - pub base0E: Rgb, - /// Deprecated, Opening/Closing Embedded Language Tags, e.g. - pub base0F: Rgb, -} - -/// A 24-bit RGB color. -#[derive(Clone, Copy)] -pub struct Rgb { - pub red: u8, - pub green: u8, - pub blue: u8, -} - -impl ColorTheme { - /// The "default dark" Base16 colorscheme, by Chris Kempson (http://chriskempson.com) - pub fn default_dark() -> ColorTheme { - ColorTheme { - base00: Rgb::from_hex("#181818").unwrap(), - base01: Rgb::from_hex("#282828").unwrap(), - base02: Rgb::from_hex("#383838").unwrap(), - base03: Rgb::from_hex("#585858").unwrap(), - base04: Rgb::from_hex("#b8b8b8").unwrap(), - base05: Rgb::from_hex("#d8d8d8").unwrap(), - base06: Rgb::from_hex("#e8e8e8").unwrap(), - base07: Rgb::from_hex("#f8f8f8").unwrap(), - base08: Rgb::from_hex("#ab4642").unwrap(), - base09: Rgb::from_hex("#dc9656").unwrap(), - base0A: Rgb::from_hex("#f7ca88").unwrap(), - base0B: Rgb::from_hex("#a1b56c").unwrap(), - base0C: Rgb::from_hex("#86c1b9").unwrap(), - base0D: Rgb::from_hex("#7cafc2").unwrap(), - base0E: Rgb::from_hex("#ba8baf").unwrap(), - base0F: Rgb::from_hex("#a16946").unwrap(), - } - } - - fn color(&self, color: Color) -> Rgb { - match color { - Color::Base00 => self.base00, - Color::Base01 => self.base01, - Color::Base02 => self.base02, - Color::Base03 => self.base03, - Color::Base04 => self.base04, - Color::Base05 => self.base05, - Color::Base06 => self.base06, - Color::Base07 => self.base07, - Color::Base08 => self.base08, - Color::Base09 => self.base09, - Color::Base0A => self.base0A, - Color::Base0B => self.base0B, - Color::Base0C => self.base0C, - Color::Base0D => self.base0D, - Color::Base0E => self.base0E, - Color::Base0F => self.base0F, - } - } - - /// The background color for a given shade, in this color theme, as a terminal256-color. - pub fn shade(&self, shade: Shade) -> Rgb { - self.color(shade.into()) - } - - /// The foreground color for a given style, in this color theme, as a terminal256-color. - pub fn foreground(&self, style: ShadedStyle) -> Rgb { - if style.reversed { - self.shade(style.shade) - } else { - self.color(style.color) - } - } - - /// The background color for a given style, in this color theme, as a terminal256-color. - pub fn background(&self, style: ShadedStyle) -> Rgb { - if style.reversed { - self.color(style.color) - } else { - self.shade(style.shade) - } - } -} - -impl Rgb { - /// Construct an Rgb color from a string of the form "#FFFFFF". - fn from_hex(hex_color: &str) -> Option { - let to_int = |inclusive_range: (usize, usize)| -> Option { - u8::from_str_radix(hex_color.get(inclusive_range.0..=inclusive_range.1)?, 16).ok() - }; - - Some(Rgb { - red: to_int((1, 2))?, - green: to_int((3, 4))?, - blue: to_int((5, 6))?, - }) - } -} diff --git a/frontends/src/frontend.rs b/frontends/src/frontend.rs deleted file mode 100644 index 8cce260..0000000 --- a/frontends/src/frontend.rs +++ /dev/null @@ -1,32 +0,0 @@ -use partial_pretty_printer::pane::PrettyWindow; -use partial_pretty_printer::Pos; - -pub use super::key::Key; -pub use super::ColorTheme; - -// TODO: mouse events - -/// An input event. -pub enum Event { - /// A key was pressed down. - KeyEvent(Key), - /// The left mouse button was pressed at the given character - /// position (relative to the terminal window). - MouseEvent(Pos), -} - -/// A front end for the editor. It knows how to render a frame and how to -/// receive keyboard events. -pub trait Frontend: Sized + PrettyWindow { - /// Construct a new frontend. - fn new(theme: ColorTheme) -> Result; - - /// Block until an event (eg. keypress) occurs, then return it. None means the event stream ended. - fn next_event(&mut self) -> Option>; - - /// Prepare to start modifying a fresh new frame. This should be called before pretty-printing. - fn start_frame(&mut self) -> Result<(), Self::Error>; - - /// Show the modified frame to the user. This should be called after pretty-printing. - fn show_frame(&mut self) -> Result<(), Self::Error>; -} diff --git a/frontends/src/key.rs b/frontends/src/key.rs deleted file mode 100644 index bd5bddb..0000000 --- a/frontends/src/key.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::fmt; - -/// A keypress. Based on the `termion` crate's `event::Key` enum. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Key { - Backspace, - Left, - Right, - Up, - Down, - Home, - End, - PageUp, - PageDown, - Delete, - Insert, - F(u8), - Char(char), - Alt(char), - Ctrl(char), - Null, - Esc, -} - -impl fmt::Display for Key { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Key::Backspace => write!(f, "Bksp"), - Key::Left => write!(f, "←"), - Key::Right => write!(f, "→"), - Key::Up => write!(f, "↑"), - Key::Down => write!(f, "↓"), - Key::Home => write!(f, "Home"), - Key::End => write!(f, "End"), - Key::PageUp => write!(f, "PgUp"), - Key::PageDown => write!(f, "PgDn"), - Key::Delete => write!(f, "Del"), - Key::Insert => write!(f, "Ins"), - Key::F(num) => write!(f, "F{}", num), - Key::Char(' ') => write!(f, "Spc"), - Key::Char(c) => write!(f, "{}", c), - Key::Alt(' ') => write!(f, "A-Spc"), - Key::Alt(c) => write!(f, "A-{}", c), - Key::Ctrl(' ') => write!(f, "C-Spc"), - Key::Ctrl(c) => write!(f, "C-{}", c), - Key::Null => write!(f, "Null"), - Key::Esc => write!(f, "Esc"), - } - } -} diff --git a/frontends/src/lib.rs b/frontends/src/lib.rs deleted file mode 100644 index fe789ff..0000000 --- a/frontends/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod color_theme; -mod frontend; -mod key; -pub mod terminal; - -pub use self::color_theme::{ColorTheme, Rgb}; -pub use self::frontend::{Event, Frontend}; -pub use self::key::Key; -pub use self::terminal::Terminal; diff --git a/frontends/src/terminal.rs b/frontends/src/terminal.rs deleted file mode 100644 index ce38166..0000000 --- a/frontends/src/terminal.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! Render to and receive events from the terminal emulator. - -mod screen_buf; -mod term_error; -use screen_buf::{ScreenBuf, ScreenOp}; -pub use term_error::TermError; - -use std::convert::TryFrom; -use std::fmt::Display; -use std::io::{self, stdin, stdout, Stdin, Stdout, Write}; - -use termion::color::{Bg, Fg, Rgb as TermionRgb}; -use termion::cursor; -use termion::event; -use termion::input::{self, MouseTerminal, TermRead}; -use termion::raw::{IntoRawMode, RawTerminal}; -use termion::screen::AlternateScreen; -use termion::style::{Bold, NoBold, NoUnderline, Reset, Underline}; - -use partial_pretty_printer::pane::PrettyWindow; -use partial_pretty_printer::{Col, Line, Pos, ShadedStyle, Size, Width}; - -use crate::frontend::{Event, Frontend, Key}; -use crate::{ColorTheme, Rgb}; - -use self::Event::{KeyEvent, MouseEvent}; - -/// Used to render to and receive events from the terminal emulator. -/// Implemented using [Termion](https://github.com/redox-os/termion). -/// Make only one. -pub struct Terminal { - stdout: AlternateScreen>>, - events: input::Events, - color_theme: ColorTheme, - buf: ScreenBuf, -} - -impl Terminal { - /// Get the current size of the actual terminal window, which might be different than the current size of the ScreenBuf. - fn terminal_window_size() -> Result { - let (width, height) = termion::terminal_size()?; - Ok(Size { - width: width as u16, - height: height as u32, - }) - } - - /// Update the screen buffer size to match the actual terminal window size. - /// If the screen buffer changes size as a result, its contents will be cleared. - fn update_size(&mut self) -> Result<(), TermError> { - let size = Self::terminal_window_size()?; - if size != self.buf.size()? { - self.buf.resize(size); - } - Ok(()) - } - - fn write(&mut self, thing: T) -> Result<(), io::Error> { - write!(self.stdout, "{}", thing) - } - - fn go_to(&mut self, pos: Pos) -> Result<(), io::Error> { - let (x, y) = pos_to_coords(pos); - self.write(cursor::Goto(x, y)) - } - - fn apply_style(&mut self, style: ShadedStyle) -> Result<(), io::Error> { - if style.bold { - self.write(Bold)?; - } else { - self.write(NoBold)?; - } - - if style.underlined { - self.write(Underline)?; - } else { - self.write(NoUnderline)?; - } - - self.write(Fg(to_termion_rgb(self.color_theme.foreground(style))))?; - self.write(Bg(to_termion_rgb(self.color_theme.background(style)))) - } -} - -impl PrettyWindow for Terminal { - type Error = TermError; - - /// Return the current size of the screen buffer, without checking the - /// actual size of the terminal window (which might have changed recently). - fn size(&self) -> Result { - self.buf.size() - } - - fn print(&mut self, pos: Pos, string: &str, style: ShadedStyle) -> Result<(), Self::Error> { - self.buf.print(pos, string, style) - } - - fn fill( - &mut self, - pos: Pos, - ch: char, - len: Width, - style: ShadedStyle, - ) -> Result<(), Self::Error> { - self.buf.fill(pos, ch, len, style) - } -} - -impl Frontend for Terminal { - fn new(theme: ColorTheme) -> Result { - let mut term = Terminal { - stdout: AlternateScreen::from(MouseTerminal::from(stdout().into_raw_mode()?)), - events: stdin().events(), - color_theme: theme, - buf: ScreenBuf::new(Terminal::terminal_window_size()?), - }; - term.write(cursor::Hide)?; - Ok(term) - } - - fn next_event(&mut self) -> Option> { - match self.events.next() { - Some(Ok(event::Event::Key(termion_key))) => Some(match Key::try_from(termion_key) { - Ok(key) => Ok(KeyEvent(key)), - Err(()) => Err(TermError::UnknownKey), - }), - - Some(Ok(event::Event::Mouse(event::MouseEvent::Press( - event::MouseButton::Left, - x, - y, - )))) => Some(Ok(MouseEvent(coords_to_pos(x, y)))), - Some(Ok(_)) => self.next_event(), - Some(Err(err)) => Some(Err(err.into())), - None => None, - } - } - - fn start_frame(&mut self) -> Result<(), TermError> { - self.update_size() - } - - fn show_frame(&mut self) -> Result<(), TermError> { - // Reset terminal's style - self.write(Reset)?; - // Update the screen from the old frame to the new frame. - let changes: Vec<_> = self.buf.drain_changes().collect(); - for op in changes { - match op { - ScreenOp::Goto(pos) => self.go_to(pos)?, - ScreenOp::Apply(style) => self.apply_style(style)?, - ScreenOp::Print(ch) => self.write(ch)?, - } - } - self.stdout.flush()?; - Ok(()) - } -} - -impl Drop for Terminal { - fn drop(&mut self) { - self.write(cursor::Show) - .expect("failed to re-show cursor when dropping terminal") - } -} - -/// Convert the native synless Rgb type into the termion one. They're both -/// defined in different crates, so we can't impl From/Into. -fn to_termion_rgb(synless_rgb: Rgb) -> TermionRgb { - TermionRgb(synless_rgb.red, synless_rgb.green, synless_rgb.blue) -} - -/// Convert a synless Pos into termion's XY coordinates. -fn pos_to_coords(pos: Pos) -> (u16, u16) { - (pos.col as u16 + 1, pos.line as u16 + 1) -} - -/// Convert termion's XY coordinates into a synless Pos. -fn coords_to_pos(x: u16, y: u16) -> Pos { - Pos { - col: x as Col - 1, - line: y as Line - 1, - } -} - -impl TryFrom for Key { - type Error = (); - fn try_from(termion_key: termion::event::Key) -> Result { - Ok(match termion_key { - termion::event::Key::Backspace => Key::Backspace, - termion::event::Key::Left => Key::Left, - termion::event::Key::Right => Key::Right, - termion::event::Key::Up => Key::Up, - termion::event::Key::Down => Key::Down, - termion::event::Key::Home => Key::Home, - termion::event::Key::End => Key::End, - termion::event::Key::PageUp => Key::PageUp, - termion::event::Key::PageDown => Key::PageDown, - termion::event::Key::Delete => Key::Delete, - termion::event::Key::Insert => Key::Insert, - termion::event::Key::F(i) => Key::F(i), - termion::event::Key::Char(c) => Key::Char(c), - termion::event::Key::Alt(c) => Key::Alt(c), - termion::event::Key::Ctrl(c) => Key::Ctrl(c), - termion::event::Key::Null => Key::Null, - termion::event::Key::Esc => Key::Esc, - _ => return Err(()), - }) - } -} diff --git a/frontends/src/terminal/screen_buf.rs b/frontends/src/terminal/screen_buf.rs deleted file mode 100644 index eb04edf..0000000 --- a/frontends/src/terminal/screen_buf.rs +++ /dev/null @@ -1,619 +0,0 @@ -use super::TermError; -use partial_pretty_printer::{pane::PrettyWindow, Pos, ShadedStyle, Size, Width}; - -/// Represents a screen full of characters. It buffers changes to the -/// characters, and can produce a set of instructions for efficiently updating -/// the screen to reflect those changes. -#[derive(Debug)] -pub struct ScreenBuf { - /// Grid of characters covering the screen. - cells: Vec>, - /// This should always contain the number of lines and cols requested by the - /// user (eg. 0-by-5), even if `cells` is empty. - size: Size, -} - -/// Represents a single character on a screen, with style properties. -#[derive(Clone, Copy, Debug, PartialEq)] -struct CharCell { - ch: char, - style: ShadedStyle, -} - -/// Stores both the new unprinted state of a character, and the old state that was last printed to the screen. -#[derive(Clone, Copy, Debug, PartialEq, Default)] -struct DoubleCharCell { - new: CharCell, - /// None if unknown (eg. because it's never been printed or the screen was just resized) - old: Option, -} - -/// Instructions for how to update a terminal. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ScreenOp { - /// Print a character at the current cursor position, and advance the cursor by 1. - Print(char), - /// Set a persistent style that will apply to anything printed, until a new style is applied. - Apply(ShadedStyle), - /// Set the cursor position. - Goto(Pos), -} - -/// States of the ScreenBufIter -enum State { - FindDirty, - CheckStyle, - PrintChar, -} - -/// An iterator that produces instructions for updating a screen to match changes in a ScreenBuf. -pub struct ScreenBufIter<'a> { - buf: &'a mut ScreenBuf, - /// The last style applied to the screen. It will persist until a new style is applied. - current_style: Option, - /// On the very first iteration, we don't know where the terminal cursor is - /// yet. We have to set it after we find the first dirty character cell, - /// before printing the character. - on_first_iteration: bool, - /// Which cell the iterator is considering (NOT the position of the - /// terminal's cursor). None means the iterator has just been constructed - /// and is pointing to immediately before the first cell of the ScreenBuf. - pos: Option, - /// Which action the iterator should do next. - next_state: State, -} - -enum FindDirtyResult { - AtEnd, - GotoDirty(ScreenOp), - AtDirty, -} - -impl ScreenBuf { - /// Create a new ScreenBuf with the given number of rows and columns of character cells - pub fn new(size: Size) -> Self { - let mut buf = ScreenBuf { - cells: Vec::new(), - size: Size { - width: 0, - height: 0, - }, - }; - buf.resize(size); - buf - } - - /// Get `ScreenOp` instructions that describe all changes to the screen buffer since the last time this method was called. - pub fn drain_changes(&mut self) -> ScreenBufIter { - ScreenBufIter { - buf: self, - current_style: None, - pos: None, - on_first_iteration: true, - next_state: State::FindDirty, - } - } - - /// Clear the screen buffer and change the number of rows and columns of character cells - pub fn resize(&mut self, size: Size) { - self.cells = Vec::new(); - let mut line = Vec::new(); - line.resize_with(size.width as usize, Default::default); - self.cells.resize(size.height as usize, line); - self.size = size; - } - - fn set_char_with_style( - &mut self, - pos: Pos, - ch: char, - style: ShadedStyle, - ) -> Result<(), TermError> { - let cell = self.get_mut(pos)?; - cell.set_char(ch); - cell.set_style(style); - Ok(()) - } - - fn get(&self, pos: Pos) -> Result { - self.cells - .get(pos.line as usize) - .and_then(|line| line.get(pos.col as usize)) - .copied() - .ok_or(TermError::OutOfBounds) - } - - fn get_mut(&mut self, pos: Pos) -> Result<&mut DoubleCharCell, TermError> { - self.cells - .get_mut(pos.line as usize) - .and_then(|line| line.get_mut(pos.col as usize)) - .ok_or(TermError::OutOfBounds) - } - - fn next_pos(&self, old_pos: Pos) -> Option { - if old_pos.col >= (self.size.width - 1) { - // At the last column of a line - if old_pos.line >= (self.size.height - 1) { - // At the last line too, that's the last position on the the screen! - None - } else { - // Go to start of next line - Some(Pos { - line: old_pos.line + 1, - col: 0, - }) - } - } else { - // Go forward 1 column - Some(Pos { - line: old_pos.line, - col: old_pos.col + 1, - }) - } - } -} - -impl PrettyWindow for ScreenBuf { - type Error = TermError; - - /// Return the current size of the screen buffer, without checking the - /// actual size of the terminal window (which might have changed recently). - fn size(&self) -> Result { - Ok(self.size) - } - - /// No newlines allowed. If the string doesn't fit between the starting - /// column position and the right edge of the screen, it's truncated and - /// and an OutOfBounds error is returned. - fn print(&mut self, mut pos: Pos, string: &str, style: ShadedStyle) -> Result<(), Self::Error> { - for ch in string.chars() { - self.set_char_with_style(pos, ch, style)?; - pos.col += 1; - } - Ok(()) - } - - fn fill( - &mut self, - mut pos: Pos, - ch: char, - len: Width, - style: ShadedStyle, - ) -> Result<(), Self::Error> { - for _ in 0..len { - self.set_char_with_style(pos, ch, style)?; - pos.col += 1; - } - Ok(()) - } -} - -impl DoubleCharCell { - fn set_char(&mut self, ch: char) { - self.new.ch = ch; - } - - fn set_style(&mut self, style: ShadedStyle) { - self.new.style = style; - } - - fn get(&self) -> CharCell { - self.new - } - - fn mark(&mut self) { - self.old = Some(self.new); - self.new = CharCell::default(); - } - - fn is_dirty(&self) -> bool { - if let Some(old) = self.old { - self.new != old - } else { - true - } - } -} - -impl Default for CharCell { - fn default() -> Self { - CharCell { - ch: ' ', - style: ShadedStyle::plain(), - } - } -} - -impl<'a> ScreenBufIter<'a> { - fn advance(&mut self) -> Option { - if let Some(p) = self.pos { - self.buf.get_mut(p).unwrap().mark(); - self.pos = Some(self.buf.next_pos(p)?); - } else { - // Start at the beginning - self.pos = Some(Pos::zero()) - } - self.pos - } - - fn pos(&self) -> Pos { - // If pos is None, the iterator is still _before_ the first cell, and not _at_ any cell. - self.pos - .expect("position not defined until advance() is called") - } - - fn cell(&self) -> CharCell { - self.buf.get(self.pos()).unwrap().get() - } - - fn at_dirty_cell(&self) -> bool { - self.buf.get(self.pos()).unwrap().is_dirty() - } - - fn find_dirty(&mut self) -> FindDirtyResult { - // Look for the next cell after this one that needs to be redisplayed. - if self.advance().is_none() { - return FindDirtyResult::AtEnd; - } - - let mut jumped = false; - while !self.at_dirty_cell() { - if self.advance().is_none() { - return FindDirtyResult::AtEnd; - } - jumped = true; - } - - // Check if we need to explicitly jump the cursor to this position. - if jumped || self.on_first_iteration { - self.on_first_iteration = false; - FindDirtyResult::GotoDirty(ScreenOp::Goto(self.pos())) - } else { - FindDirtyResult::AtDirty - } - } - - fn check_style(&mut self) -> Option { - // Check if it has a different style than the last one we applied. - let new_style = self.cell().style; - let style_changed = match self.current_style { - None => true, - Some(s) => s != new_style, - }; - - if style_changed { - self.current_style = Some(new_style); - assert!(!self.on_first_iteration); - Some(ScreenOp::Apply(new_style)) - } else { - None - } - } - - fn print_char(&mut self) -> ScreenOp { - // Finally, print the character itself - assert!(!self.on_first_iteration); - ScreenOp::Print(self.cell().ch) - } -} - -impl<'a> Iterator for ScreenBufIter<'a> { - type Item = ScreenOp; - fn next(&mut self) -> Option { - loop { - match self.next_state { - State::FindDirty => { - self.next_state = State::CheckStyle; - match self.find_dirty() { - FindDirtyResult::GotoDirty(op) => { - return Some(op); - } - FindDirtyResult::AtDirty => (), // No Goto op needed, continue to next state - FindDirtyResult::AtEnd => { - // Done! Reached the end of the buffer. - return None; - } - } - } - State::CheckStyle => { - self.next_state = State::PrintChar; - let op = self.check_style(); - if op.is_some() { - return op; - } - } - State::PrintChar => { - self.next_state = State::FindDirty; - return Some(self.print_char()); - } - } - } - } -} - -#[cfg(test)] -mod screen_buf_tests { - use super::*; - use partial_pretty_printer::{Color, Pos, Shade, ShadedStyle, Size, Style}; - - fn assert_out_of_bounds(result: Result<(), TermError>) { - match result { - Err(TermError::OutOfBounds) => (), - x => panic!("expected OutOfBounds error, got {:?}", x), - } - } - - fn assert_resized(buf: &mut ScreenBuf, size: Size, good_pos: &[Pos], bad_pos: &[Pos]) { - buf.resize(size); - assert_eq!(buf.size().unwrap(), size); - for &pos in good_pos { - buf.set_char_with_style(pos, 'x', ShadedStyle::plain()) - .unwrap_or_else(|_| panic!("pos {} out-of-bounds of buf with size {}", pos, size)); - } - for &pos in bad_pos { - assert_out_of_bounds(buf.set_char_with_style(pos, 'x', ShadedStyle::plain())); - } - } - - #[test] - fn test_resize() { - let c0r0 = Pos { col: 0, line: 0 }; - let c0r1 = Pos { col: 0, line: 1 }; - let c1r0 = Pos { col: 1, line: 0 }; - let c1r1 = Pos { col: 1, line: 1 }; - let c5r8 = Pos { col: 5, line: 8 }; - let c5r7 = Pos { col: 5, line: 7 }; - let c4r7 = Pos { col: 4, line: 7 }; - let c4r8 = Pos { col: 4, line: 8 }; - - let mut buf = ScreenBuf::new(Size { - height: 1, - width: 1, - }); - assert_eq!( - buf.size().unwrap(), - Size { - height: 1, - width: 1, - }, - ); - assert_resized( - &mut buf, - Size { - width: 1, - height: 0, - }, - &[], - &[c0r0, c1r0, c0r1], - ); - assert_resized( - &mut buf, - Size { - width: 0, - height: 1, - }, - &[], - &[c0r0, c1r0, c0r1], - ); - assert_resized( - &mut buf, - Size { - width: 1, - height: 1, - }, - &[c0r0], - &[c1r0, c0r1, c1r1], - ); - assert_resized( - &mut buf, - Size { - width: 5, - height: 8, - }, - &[c0r0, c1r0, c0r1, c1r1, c4r7], - &[c5r8, c4r8, c5r7], - ); - } - - #[test] - fn test_simple() { - let style1 = ShadedStyle::new(Style::color(Color::Base09), Shade::background()); - let mut buf = ScreenBuf::new(Size { - width: 3, - height: 2, - }); - - let pos = Pos { col: 2, line: 0 }; - buf.print(pos, "x", style1).unwrap(); - let mut actual_ops: Vec<_> = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos::zero()), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Apply(style1), - ScreenOp::Print('x'), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ] - ); - actual_ops = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(pos), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ] - ); - } - - #[test] - fn test_no_change() { - let style1 = ShadedStyle::new(Style::color(Color::Base09), Shade::background()); - let mut buf = ScreenBuf::new(Size { - width: 3, - height: 2, - }); - - let pos = Pos { col: 2, line: 0 }; - buf.print(pos, "x", style1).unwrap(); - let mut actual_ops: Vec<_> = buf.drain_changes().collect(); - - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos::zero()), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Apply(style1), - ScreenOp::Print('x'), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ] - ); - - // Print same thing as before - buf.print(pos, "x", style1).unwrap(); - actual_ops = buf.drain_changes().collect(); - assert_eq!(actual_ops, vec![]); - } - - #[test] - fn test_shorten() { - let style1 = ShadedStyle::new(Style::color(Color::Base09), Shade::background()); - - let mut buf = ScreenBuf::new(Size { - width: 3, - height: 1, - }); - - buf.print(Pos::zero(), "xyz", style1).unwrap(); - let mut actual_ops: Vec<_> = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos::zero()), - ScreenOp::Apply(style1), - ScreenOp::Print('x'), - ScreenOp::Print('y'), - ScreenOp::Print('z'), - ] - ); - - buf.print(Pos::zero(), "xy", style1).unwrap(); - actual_ops = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos { col: 2, line: 0 }), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ] - ); - - buf.print(Pos::zero(), "x", style1).unwrap(); - actual_ops = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos { col: 1, line: 0 }), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ] - ); - - buf.print(Pos::zero(), "xy", style1).unwrap(); - actual_ops = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos { col: 1, line: 0 }), - ScreenOp::Apply(style1), - ScreenOp::Print('y'), - ] - ); - } - - #[test] - fn test_complex() { - let style1 = ShadedStyle::new(Style::color(Color::Base09), Shade::background()); - let style2 = ShadedStyle::new(Style::color(Color::Base0C), Shade::background()); - - let mut buf = ScreenBuf::new(Size { - width: 3, - height: 4, - }); - - buf.print(Pos { col: 1, line: 0 }, "fo", style1).unwrap(); - buf.print(Pos { col: 0, line: 1 }, "oba", style1).unwrap(); - buf.print(Pos { col: 0, line: 2 }, "r", style1).unwrap(); - - buf.print(Pos { col: 0, line: 1 }, "OB", style2).unwrap(); - - buf.print(Pos { col: 2, line: 3 }, "$", ShadedStyle::plain()) - .unwrap(); - - let mut actual_ops: Vec<_> = buf.drain_changes().collect(); - - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos::zero()), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Apply(style1), - ScreenOp::Print('f'), - ScreenOp::Print('o'), - ScreenOp::Apply(style2), - ScreenOp::Print('O'), - ScreenOp::Print('B'), - ScreenOp::Apply(style1), - ScreenOp::Print('a'), - ScreenOp::Print('r'), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print('$'), - ] - ); - actual_ops = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos { col: 1, line: 0 }), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Print(' '), - ScreenOp::Goto(Pos { col: 2, line: 3 }), - ScreenOp::Print(' '), - ] - ); - - buf.set_char_with_style(Pos { col: 2, line: 3 }, '!', ShadedStyle::plain()) - .unwrap(); - - actual_ops = buf.drain_changes().collect(); - assert_eq!( - actual_ops, - vec![ - ScreenOp::Goto(Pos { col: 2, line: 3 }), - ScreenOp::Apply(ShadedStyle::plain()), - ScreenOp::Print('!'), - ] - ); - } -} diff --git a/frontends/src/terminal/term_error.rs b/frontends/src/terminal/term_error.rs deleted file mode 100644 index 6433a52..0000000 --- a/frontends/src/terminal/term_error.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::io; - -#[derive(thiserror::Error, Debug)] -pub enum TermError { - #[error("terminal input/output error: {0}")] - Io(#[from] io::Error), - - #[error("position outside window boundary")] - OutOfBounds, - - #[error("unknown key pressed")] - UnknownKey, -} diff --git a/language/Cargo.toml b/language/Cargo.toml deleted file mode 100644 index ab92a84..0000000 --- a/language/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -workspace = ".." -name = "language" -version = "0.1.0" -authors = ["Justin Pombrio "] -edition = "2018" - -[dependencies] -forest = { path = "../forest" } -partial-pretty-printer = { git = "https://github.com/justinpombrio/partial-pretty-printer" } -lazy_static = "*" -typed-arena = "*" diff --git a/language/src/ast/ast.rs b/language/src/ast/ast.rs deleted file mode 100644 index f7b5a90..0000000 --- a/language/src/ast/ast.rs +++ /dev/null @@ -1,256 +0,0 @@ -use super::text::Text; -use crate::language::{Arity, Construct, ConstructId, Grammar}; -use forest::{Bookmark, Tree}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Id(pub(super) usize); - -#[derive(Clone, Copy)] -pub struct NodeData<'l> { - pub(super) id: Id, - pub(super) grammar: &'l Grammar, - pub(super) construct_id: ConstructId, -} - -/// An Abstract Syntax Tree. -/// -/// More specifically, this is a mutable reference _to a node_ in an AST. -/// -/// This value owns the entire tree. When it is dropped, the tree is deleted. -/// -/// It also grants write access to the tree. Use [`borrow`](#method.borrow) to -/// obtain a shared reference with read-only access. -pub struct Ast<'l> { - pub(super) tree: Tree, Text>, -} - -pub enum AstCase<'a, 'l> { - Texty(TextyAst<'a, 'l>), - Fixed(FixedAst<'a, 'l>), - Listy(ListyAst<'a, 'l>), -} - -/// A wrapper around an `Ast` with `Texty` arity. -pub struct TextyAst<'a, 'l>(&'a mut Ast<'l>); - -/// A wrapper around an `Ast` with `Fixed` arity. -pub struct FixedAst<'a, 'l>(&'a mut Ast<'l>); - -/// A wrapper around an `Ast` with `Listy` arity. -pub struct ListyAst<'a, 'l>(&'a mut Ast<'l>); - -impl<'l> Ast<'l> { - pub(super) fn new(tree: Tree, Text>) -> Ast<'l> { - Ast { tree } - } - - /// Get the arity of this node. - pub fn arity(&self) -> &Arity { - let node = self.tree.data(); - &node.grammar.construct(node.construct_id).arity - } - - /// Get the syntactic construct this node falls into. - pub fn construct(&self) -> &'l Construct { - let node = self.tree.data(); - &node.grammar.construct(node.construct_id) - } - - /// Determine this node's index among its siblings. Returns `0` when at the - /// root. For Mixed parents, counts both text and tree children. - pub fn index(&self) -> usize { - self.tree.index() - } - - /// Determine the number of siblings that this node has, including itself. - /// For Mixed parents, counts both text and tree children. When at the root, - /// returns 1. - pub fn num_siblings(&self) -> usize { - self.tree.num_siblings() - } - - /// Returns `true` if this is the root of the tree, and `false` if - /// it isn't (and thus this node has a parent). - pub fn is_at_root(&self) -> bool { - self.tree.is_at_root() - } - - /// Return the number of children this node has. For a Fixed node, this is - /// its arity. For a Listy node, this is its current number of children. - /// For text, this is considered 0. - fn num_children(&self) -> usize { - match self.arity() { - Arity::Texty => 0, - Arity::Fixed(sorts) => sorts.len(), - Arity::Listy(_) => self.tree.num_children(), - } - } - - /// Go to the parent of this node. Returns this node's index among its - /// siblings (so that you can return to it later). - /// - /// # Panics - /// - /// Panics if this is the root of the tree, and there is no parent. - pub fn goto_parent(&mut self) -> usize { - self.tree.goto_parent() - } - - /// Go to the i'th child of this node's parent. - /// - /// # Panics - /// - /// Panics if the index is out of bounds, or if this node has no parent. - pub fn goto_sibling(&mut self, i: usize) { - self.tree.goto_parent(); - self.tree.goto_child(i); - } - - /// Go to this tree's root. - pub fn goto_root(&mut self) { - self.tree.goto_root() - } - - /// Save a bookmark to return to later. - pub fn bookmark(&mut self) -> Bookmark { - self.tree.bookmark() - } - - /// Jump to a previously saved bookmark, as long as that - /// bookmark's node is present somewhere in this tree. This will - /// work even if the Tree has been modified since the bookmark was - /// created. However, it will return `false` if the bookmark's node - /// has since been deleted, or if it is currently located in a - /// different tree. - pub fn goto_bookmark(&mut self, mark: Bookmark) -> bool { - self.tree.goto_bookmark(mark) - } - - /// Produce a more specific type, based on this node's arity (fixed, texty, or listy). More - /// methods are available on these specialized types. - pub fn case<'a>(&'a mut self) -> AstCase<'a, 'l> { - match self.arity() { - Arity::Texty => AstCase::Texty(TextyAst(self)), - Arity::Fixed(_) => AstCase::Fixed(FixedAst(self)), - Arity::Listy(_) => AstCase::Listy(ListyAst(self)), - } - } -} - -impl<'a, 'l> AstCase<'a, 'l> { - pub fn unwrap_text(self) -> TextyAst<'a, 'l> { - match self { - AstCase::Texty(ast) => ast, - _ => panic!("expected AstCase::Texty"), - } - } - pub fn unwrap_fixed(self) -> FixedAst<'a, 'l> { - match self { - AstCase::Fixed(ast) => ast, - _ => panic!("expected AstCase::Fixed"), - } - } - pub fn unwrap_flexible(self) -> ListyAst<'a, 'l> { - match self { - AstCase::Listy(ast) => ast, - _ => panic!("expected AstCase::Listy"), - } - } -} - -impl<'a, 'l> TextyAst<'a, 'l> { - /// Call the closure, giving it read-access to this node's text. - pub fn with_text(&self, func: impl FnOnce(&Text) -> R) -> R { - self.0.tree.with_leaf(func) - } - - /// Call the closure, giving it write-access to this node's text. - pub fn with_text_mut(&mut self, func: impl FnOnce(&mut Text) -> R) -> R { - self.0.tree.with_leaf_mut(func) - } -} - -impl<'a, 'l> FixedAst<'a, 'l> { - /// Return the number of children this node has. For a Fixed node, this is - /// the same as its arity. - pub fn num_children(&self) -> usize { - self.0.num_children() - } - - /// Go to this node's i'th child. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn goto_child(&mut self, i: usize) { - self.0.tree.goto_child(i) - } - - /// Replace this node's `i`th child with the `ast`. Return the old child if successful. If - /// `ast` cannot be placed here because it has the wrong Sort, return it as `Err(ast)`. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn replace_child(&mut self, i: usize, ast: Ast<'l>) -> Result, Ast<'l>> { - if !self.0.arity().child_sort(i).accepts(&ast.construct().sort) { - // This ast can't go here, it has the wrong Sort! Send it back. - return Err(ast); - } - Ok(Ast::new(self.0.tree.replace_child(i, ast.tree))) - } -} - -impl<'a, 'l> ListyAst<'a, 'l> { - /// Return the number of children this node currently has. - pub fn num_children(&self) -> usize { - self.0.num_children() - } - - /// Go to this node's i'th child. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn goto_child(&mut self, i: usize) { - self.0.tree.goto_child(i) - } - - /// Replace this node's `i`th child. If successful, return the replaced - /// child. Otherwise, return the given ast as `Err(ast)`. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn replace_child(&mut self, i: usize, ast: Ast<'l>) -> Result, Ast<'l>> { - if !self.0.arity().child_sort(i).accepts(&ast.construct().sort) { - // This ast can't go here, it has the wrong Sort! Send it back. - return Err(ast); - } - Ok(Ast::new(self.0.tree.replace_child(i, ast.tree))) - } - - /// Insert `ast` as the `i`th child of this node. If it cannot be inserted because it has the - /// wrong sort, return the given ast as `Err(ast)`. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn insert_child(&mut self, i: usize, ast: Ast<'l>) -> Result<(), Ast<'l>> { - if !self.0.arity().child_sort(i).accepts(&ast.construct().sort) { - // This tree can't go here, it has the wrong Sort! Send it back. - return Err(ast); - } - self.0.tree.insert_child(i, ast.tree); - Ok(()) - } - - /// Remove and return the `i`th child of this node. - /// - /// # Panics - /// - /// Panics if `i` is out of bounds. - pub fn remove_child(&mut self, i: usize) -> Ast<'l> { - Ast::new(self.0.tree.remove_child(i)) - } -} diff --git a/language/src/ast/ast_forest.rs b/language/src/ast/ast_forest.rs deleted file mode 100644 index 062b42b..0000000 --- a/language/src/ast/ast_forest.rs +++ /dev/null @@ -1,81 +0,0 @@ -use super::ast::{Ast, Id, NodeData}; -use super::ast_ref::AstRef; -use super::text::Text; -use crate::language::LanguageSet; -use crate::language::{Arity, ConstructId, Grammar}; -use forest::Forest; - -/// All [`Asts`] belong to an `AstForest`. -/// -/// It is your responsibility to ensure that `Ast`s are kept with the forest they came from. The -/// methods on `Ast`s may panic or worse if you use them on a different forest. -pub struct AstForest<'l> { - pub(super) lang: LanguageSet<'l>, - forest: Forest, Text>, - next_id: Id, -} - -impl<'l> AstForest<'l> { - /// Construct a new, empty, forest. - pub fn new(language_set: LanguageSet<'l>) -> AstForest<'l> { - AstForest { - lang: language_set, - forest: Forest::new(), - next_id: Id(0), - } - } - - /// Create a new `hole` node in this forest. - // TODO: 'cept for Id, this can take &self! Is that useful? - pub fn new_hole(&mut self) -> Ast<'l> { - let (grammar, construct_id) = self.lang.builtin_hole_info(); - let node = NodeData { - grammar, - construct_id, - id: self.next_id(), - }; - Ast::new(self.forest.new_branch(node, vec![])) - } - - pub fn new_tree(&mut self, grammar: &'l Grammar, construct_id: ConstructId) -> Ast<'l> { - let construct = grammar.construct(construct_id); - let node = NodeData { - grammar, - construct_id, - id: self.next_id(), - }; - match &construct.arity { - Arity::Texty => Ast::new(self.forest.new_leaf(node, Text::new_inactive())), - Arity::Fixed(sorts) => { - let children = (0..sorts.len()) - .map(|_| self.new_hole().tree) - .collect::>(); - Ast::new(self.forest.new_branch(node, children)) - } - Arity::Listy(_) => Ast::new(self.forest.new_branch(node, vec![])), - } - } - - pub fn borrow(&self, ast: &Ast<'l>, func: impl FnOnce(AstRef<'_, 'l>) -> R) -> R { - ast.tree.borrow(|tree_ref| { - func(AstRef { - lang: &self.lang, - tree_ref: tree_ref, - }) - }) - } - - /* - pub fn borrow<'f>(&'f self, ast: &'f Ast<'l>) -> AstRef<'f, 'l> { - AstRef { - lang: &self.lang, - tree_ref: ast.tree.borrow(), - } - } - */ - - fn next_id(&mut self) -> Id { - self.next_id.0 += 1; - Id(self.next_id.0) - } -} diff --git a/language/src/ast/ast_ref.rs b/language/src/ast/ast_ref.rs deleted file mode 100644 index 5df193e..0000000 --- a/language/src/ast/ast_ref.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::ast::{Id, NodeData}; -use super::text::Text; -use crate::language::{Arity, LanguageSet}; -use forest::{Bookmark, TreeRef}; -use partial_pretty_printer::{PrettyDoc, ValidNotation}; - -/// An immutable reference to a node in an AST. -#[derive(Clone, Copy)] -pub struct AstRef<'f, 'l> { - pub(super) lang: &'f LanguageSet<'l>, - pub(super) tree_ref: TreeRef<'f, NodeData<'l>, Text>, -} - -impl<'f, 'l> AstRef<'f, 'l> { - /// Get the arity of this node. - pub fn arity(self) -> &'l Arity { - let node = self.tree_ref.data(); - &node.grammar.construct(node.construct_id).arity - } - - /// Save a bookmark to return to later. - pub fn bookmark(self) -> Bookmark { - self.tree_ref.bookmark() - } - - /// Return to a previously saved bookmark, as long as that bookmark's node - /// is present somewhere in this tree. This will work even if the Tree has - /// been modified since the bookmark was created. However, this method will - /// return `None` if the bookmark's node has since been deleted, or if it is - /// currently located in a different tree. - pub fn lookup_bookmark(self, mark: Bookmark) -> Option> { - self.tree_ref.lookup_bookmark(mark).map(|tr| AstRef { - lang: self.lang, - tree_ref: tr, - }) - } -} - -impl<'d> PrettyDoc<'d> for AstRef<'d, 'd> { - type Id = Id; - - fn id(self) -> Self::Id { - self.tree_ref.data().id - } - - fn notation(self) -> &'d ValidNotation { - let node = self.tree_ref.data(); - // TODO: No HashMap lookups while pretty printing! - let lang_name = &node.grammar.language_name(); - &self - .lang - .current_notation_set(lang_name) - .lookup(node.construct_id) - } - - /// Get this node's number of children, or `None` if it contains text instead. - fn num_children(self) -> Option { - if self.tree_ref.is_leaf() { - None - } else { - Some(self.tree_ref.num_children()) - } - } - - /// Get this node's text, or panic. - fn unwrap_text(self) -> &'d str { - assert!(self.arity().is_texty()); - self.tree_ref.leaf().as_str() - } - - /// Get this node's i'th child, or panic. - fn unwrap_child(self, i: usize) -> Self { - AstRef { - lang: self.lang, - tree_ref: self.tree_ref.child(i), - } - } -} diff --git a/language/src/ast/mod.rs b/language/src/ast/mod.rs deleted file mode 100644 index 116e0cd..0000000 --- a/language/src/ast/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod ast; -mod ast_forest; -mod ast_ref; -mod text; - -pub use self::ast::{Ast, AstCase, FixedAst, ListyAst, TextyAst}; -pub use self::ast_forest::AstForest; -pub use self::ast_ref::AstRef; -pub use text::Text; diff --git a/language/src/ast/text.rs b/language/src/ast/text.rs deleted file mode 100644 index 470c27b..0000000 --- a/language/src/ast/text.rs +++ /dev/null @@ -1,252 +0,0 @@ -use std::iter; - -/// This enum provides a consistent interface to a piece of text, while allowing -/// various optimizations that depend on whether the text is actively being -/// edited or not. -#[derive(Clone, Debug)] -pub enum Text { - /// Text that is not currently being edited. - Inactive(String), - /// Text that is currently being edited. - Active(ActiveText), -} - -/// Text that is currently being edited. -#[derive(Clone, Debug)] -pub struct ActiveText(String); - -impl Text { - /// Construct a new `Text` in an inactive state. - pub fn new_inactive() -> Self { - Text::Inactive(String::new()) - } - - /// Switch from an inactive to active state, so the text can be edited. - /// - /// # Panics - /// - /// Panics if the `Text` is already active. - pub fn activate(&mut self) { - *self = match self { - Text::Inactive(s) => Text::Active(ActiveText(s.to_owned())), - _ => panic!("text is already active"), - } - } - - /// Switch from an active to inactive state, so the text can no longer be edited. - /// - /// # Panics - /// - /// Panics if the `Text` is already inactive. - pub fn deactivate(&mut self) { - *self = match self { - Text::Active(ActiveText(s)) => Text::Inactive(s.to_owned()), - _ => panic!("text is already inactive"), - } - } - - /// Get a reference to the underlying string. Works both when active and inactive. - pub fn as_str(&self) -> &str { - match self { - Text::Active(ref s) => &s.0, - Text::Inactive(ref s) => s, - } - } - - /// Return the length of the text in characters. Works both when active and inactive. - pub fn num_chars(&self) -> usize { - match self { - Text::Active(ref s) => s.num_chars(), - Text::Inactive(ref s) => s.chars().count(), - } - } - - /// Insert a new character at the given index. - /// - /// # Panics - /// - /// Panics if the `Text` is inactive or the index is greater than the number - /// of characters in the text. - pub fn insert(&mut self, char_index: usize, character: char) { - match self { - Text::Active(active_text) => active_text.insert(char_index, character), - _ => panic!("Text::insert - tried to edit inactive text"), - } - } - - /// Remove and return the character at the given index. - /// - /// # Panics - /// - /// Panics if the `Text` is inactive or the index is greater than the number - /// of characters in the text. - pub fn delete(&mut self, char_index: usize) -> char { - match self { - Text::Active(active_text) => active_text.delete(char_index), - _ => panic!("Text::delete - tried to edit inactive text"), - } - } - - /// Set the text to the given string, replacing the current contents. - /// - /// # Panics - /// - /// Panics if the `Text` is inactive. - pub fn set(&mut self, s: String) { - match self { - Text::Active(active_text) => active_text.set(s), - _ => panic!("Text::set - tried to edit inactive text"), - } - } -} - -impl AsRef for Text { - fn as_ref(&self) -> &str { - match self { - Text::Active(s) => &s.0, - Text::Inactive(s) => s, - } - } -} - -impl ActiveText { - fn num_chars(&self) -> usize { - self.0.chars().count() - } - - fn delete(&mut self, char_index: usize) -> char { - // If char_index is larger than that, let byte_index() panic! - self.0.remove(self.byte_index(char_index)) - } - - fn insert(&mut self, char_index: usize, character: char) { - self.0.insert(self.byte_index(char_index), character); - } - - fn set(&mut self, s: String) { - self.0 = s; - } - - fn byte_index(&self, char_index: usize) -> usize { - self.0 - .char_indices() - .map(|(i, _)| i) - .chain(iter::once(self.0.len())) - .nth(char_index) - .expect("ActiveText::byte_index - character index is out of range") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic(expected = "Text::insert - tried to edit inactive text")] - fn test_inactive_insert() { - let mut t = Text::new_inactive(); - t.insert(0, 'a'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_insert() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(1, 'c'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_insert2() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.insert(2, 'c'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_insert3() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.insert(3, 'd'); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_delete() { - let mut t = Text::new_inactive(); - t.activate(); - t.delete(1); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_delete2() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.delete(2); - } - - #[test] - #[should_panic(expected = "ActiveText::byte_index - character index is out of range")] - fn test_active_invalid_delete3() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'a'); - t.delete(3); - } - - #[test] - #[allow(clippy::cognitive_complexity)] - fn test_active_edit() { - let mut t = Text::new_inactive(); - t.activate(); - t.insert(0, 'b'); - t.insert(0, 'a'); - t.insert(2, 'd'); - assert_eq!(t.as_str(), "abd"); - assert_eq!(t.num_chars(), 3); - assert_eq!(t.as_str(), "abd"); - assert_eq!(t.num_chars(), 3); - - t.insert(2, 'c'); - assert_eq!(t.as_str(), "abcd"); - - t.insert(4, 'e'); - assert_eq!(t.as_str(), "abcde"); - - t.deactivate(); - assert_eq!(t.num_chars(), 5); - assert_eq!(t.as_str(), "abcde"); - t.activate(); - assert_eq!(t.num_chars(), 5); - assert_eq!(t.as_str(), "abcde"); - - assert_eq!(t.delete(2), 'c'); - assert_eq!(t.as_str(), "abde"); - - assert_eq!(t.delete(1), 'b'); - assert_eq!(t.as_str(), "ade"); - - assert_eq!(t.delete(0), 'a'); - assert_eq!(t.num_chars(), 2); - assert_eq!(t.as_str(), "de"); - - assert_eq!(t.delete(1), 'e'); - assert_eq!(t.as_str(), "d"); - assert_eq!(t.num_chars(), 1); - - assert_eq!(t.delete(0), 'd'); - assert_eq!(t.num_chars(), 0); - assert_eq!(t.as_str(), ""); - - t.insert(0, 'a'); - assert_eq!(t.delete(0), 'a'); - assert_eq!(t.as_str(), ""); - assert_eq!(t.num_chars(), 0); - } -} diff --git a/language/src/language/construct.rs b/language/src/language/construct.rs deleted file mode 100644 index 606e0fa..0000000 --- a/language/src/language/construct.rs +++ /dev/null @@ -1,95 +0,0 @@ -use utility::spanic; - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Sort { - Any, - Named(String), -} - -/// A kind of node that can appear in a document. -#[derive(Debug)] -pub struct Construct { - pub name: String, - pub sort: Sort, - pub arity: Arity, - pub key: Option, -} - -/// The sorts of children that a node is allowed to contain. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Arity { - /// Designates a pure text node. - Texty, - /// Designates a node containing a fixed number of tree children. - /// `Vec` contains the `Sort`s of each of its children respectively. - Fixed(Vec), - /// Designates a node containing any number of tree children, - /// all of the same `Sort`. - Listy(Sort), -} - -/// Like `Arity`, but without any data in the variants. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ArityType { - Texty, - Fixed, - Listy, -} - -impl Sort { - /// Construct a new "Any" sort. - pub fn any() -> Sort { - Sort::Any - } - - pub fn named(sort_name: &str) -> Sort { - Sort::Named(sort_name.to_owned()) - } - - /// Return true if a hole with this sort can accept a node with the given sort. - pub fn accepts(&self, candidate: &Sort) -> bool { - match (self, candidate) { - (Sort::Any, _) => true, - (_, Sort::Any) => true, - (Sort::Named(x), Sort::Named(y)) => x == y, - } - } -} - -impl Arity { - pub fn is_texty(&self) -> bool { - matches!(self, Arity::Texty) - } - - pub fn is_fixed(&self) -> bool { - matches!(self, Arity::Fixed(_)) - } - - pub fn is_listy(&self) -> bool { - matches!(self, Arity::Listy(_)) - } - - /// Get the `Sort` of the `i`th child. For listy nodes, get the `Sort` required of all tree - /// children, ignoring `i`. - /// - /// # Panics - /// - /// Panics if nodes of this arity cannot have an `i`th child. - pub fn child_sort(&self, i: usize) -> &Sort { - match self { - Arity::Listy(sort) => sort, - Arity::Fixed(sorts) => sorts.get(i).unwrap_or_else(|| { - spanic!("child_sort - fixed node has only {} children", sorts.len()) - }), - _ => spanic!("child_sort - node has no children"), - } - } - - pub fn arity_type(&self) -> ArityType { - match self { - Arity::Texty => ArityType::Texty, - Arity::Fixed(_) => ArityType::Fixed, - Arity::Listy(_) => ArityType::Listy, - } - } -} diff --git a/language/src/language/language_set.rs b/language/src/language/language_set.rs deleted file mode 100644 index 3629702..0000000 --- a/language/src/language/language_set.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! An editable language. - -use super::construct::{Arity, Construct, Sort}; -use partial_pretty_printer::ValidNotation; -use std::collections::HashMap; -use std::iter::Iterator; -use typed_arena::Arena; -use utility::spanic; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ConstructId(u32); - -pub struct LanguageSet<'l> { - grammar_storage: &'l Arena, - notation_storage: &'l Arena, - /// Language name -> Language - languages: HashMap>, -} - -struct Language<'l> { - grammar: &'l Grammar, - current_notation: &'l NotationSet, - all_notations: HashMap, -} - -pub struct Grammar { - language_name: String, - sorts: Vec, - /// ConstructId -> Construct - constructs: Vec, - constructs_of_sort: HashMap>, - keymap: HashMap, -} - -pub struct LanguageStorage { - grammars: Arena, - notations: Arena, -} - -pub struct NotationSet { - name: String, - /// Construct id -> ValidNotation - notations: Vec, -} - -impl<'l> LanguageSet<'l> { - pub fn new(storage: &'l mut LanguageStorage) -> LanguageSet<'l> { - let (builtins_grammar, builtins_notation) = make_builtins_language(); - let mut langs = LanguageSet { - grammar_storage: &storage.grammars, - notation_storage: &storage.notations, - languages: HashMap::new(), - }; - langs.add_language(builtins_grammar, "Default", builtins_notation); - langs - } - - pub fn add_language( - &mut self, - grammar: Grammar, - default_notation_name: &str, - default_notation: Vec<(String, ValidNotation)>, - ) { - let grammar: &'l Grammar = self.grammar_storage.alloc(grammar); - let default_notation = - NotationSet::new(default_notation_name.to_owned(), &grammar, default_notation); - let default_notation: &'l NotationSet = self.notation_storage.alloc(default_notation); - let mut all_notations = HashMap::new(); - all_notations.insert(grammar.language_name.clone(), default_notation); - self.languages.insert( - grammar.language_name.clone(), - Language { - grammar, - current_notation: default_notation, - all_notations, - }, - ); - } - - pub fn add_notation_set( - &mut self, - language_name: &str, - name: &str, - notations: Vec<(String, ValidNotation)>, - ) { - let language = self.languages.get_mut(language_name).unwrap(); - let notation_set = NotationSet::new(name.to_owned(), language.grammar, notations); - let notation_set: &'l NotationSet = self.notation_storage.alloc(notation_set); - language.all_notations.insert(name.to_owned(), notation_set); - } - - pub fn current_notation_set(&self, language_name: &str) -> &'l NotationSet { - self.languages[language_name].current_notation - } - - pub fn all_notation_sets(&self, language_name: &str) -> impl Iterator { - self.languages[language_name] - .all_notations - .values() - .copied() - } - - pub fn switch_notation_set(&mut self, language_name: &str, notation_set_name: &str) { - let language = self.languages.get_mut(language_name).unwrap(); - language.current_notation = language.all_notations[notation_set_name]; - } - - pub fn builtin_hole_info(&self) -> (&'l Grammar, ConstructId) { - // TODO: Avoid magic constants? - let language = &self.languages["SynlessBuiltins"]; - (language.grammar, ConstructId(0)) - } - - pub fn builtin_root_info(&self) -> (&'l Grammar, ConstructId) { - let language = &self.languages["SynlessBuiltins"]; - (language.grammar, ConstructId(1)) - } -} - -fn make_builtins_language() -> (Grammar, Vec<(String, ValidNotation)>) { - use partial_pretty_printer::notation_constructors::{child, lit}; - use partial_pretty_printer::{Color, Style}; - - let mut grammar = Grammar::new("SynlessBuiltins"); - grammar.add_construct("Hole", Sort::any(), Arity::Fixed(vec![]), Some('?')); - grammar.add_construct( - "Root", - Sort::named("Root"), - Arity::Fixed(vec![Sort::any()]), - None, - ); - - let hole_notation = lit( - "?", - Style { - color: Color::Base0C, - bold: true, - underlined: false, - reversed: true, - }, - ) - .validate() - .expect("builtin hole notation is not valid"); - - let root_notation = child(0) - .validate() - .expect("builtin root notation is not valid"); - - let notations = vec![ - ("Hole".to_owned(), hole_notation), - ("Root".to_owned(), root_notation), - ]; - (grammar, notations) -} - -impl NotationSet { - pub fn new( - name: String, - grammar: &Grammar, - notations: Vec<(String, ValidNotation)>, - ) -> NotationSet { - let mut notations_map = notations.into_iter().collect::>(); - let notations = grammar - .constructs - .iter() - .map(|con| notations_map.remove(&con.name).unwrap()) - .collect::>(); - NotationSet { name, notations } - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn lookup(&self, construct_id: ConstructId) -> &ValidNotation { - &self.notations[construct_id.0 as usize] - } -} - -impl Grammar { - pub fn new(language_name: &str) -> Grammar { - Grammar { - language_name: language_name.to_owned(), - sorts: vec![], - constructs: vec![], - constructs_of_sort: HashMap::new(), - keymap: HashMap::new(), - } - } - - pub fn language_name(&self) -> &str { - &self.language_name - } - - pub fn lookup_key(&self, key: char) -> Option<&Construct> { - Some(&self.constructs[self.keymap.get(&key)?.0 as usize]) - } - - pub fn construct(&self, construct_id: ConstructId) -> &Construct { - &self.constructs[construct_id.0 as usize] - } - - pub fn keymap(&self) -> impl Iterator { - self.keymap - .iter() - .map(move |(ch, con)| (*ch, self.construct(*con).name.as_ref())) - } - - pub fn constructs(&self) -> impl Iterator { - self.constructs.iter() - } - - fn add_sort(&mut self, sort: &Sort) { - if !self.sorts.contains(&sort) { - self.sorts.push(sort.to_owned()); - self.constructs_of_sort.insert(sort.to_owned(), vec![]); - } - } - - pub fn add_construct(&mut self, name: &str, sort: Sort, arity: Arity, key: Option) { - // Add the sort - self.add_sort(&sort); - - // Add the construct - let construct = Construct { - name: name.to_owned(), - sort: sort.clone(), - arity, - key, - }; - let construct_id = ConstructId(self.constructs.len() as u32); - self.constructs.push(construct); - - // Extend the keymap - if let Some(key) = key { - let duplicate = self.keymap.insert(key, construct_id); - if duplicate.is_some() { - spanic!("Duplicate key '{}'", key); - } - } - - // Extend the construct list for the sort - let cons_list = self.constructs_of_sort.get_mut(&sort).unwrap(); - if !cons_list.contains(&construct_id) { - cons_list.push(construct_id); - } - } -} diff --git a/language/src/language/mod.rs b/language/src/language/mod.rs deleted file mode 100644 index fde2c81..0000000 --- a/language/src/language/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod construct; -mod language_set; - -pub use self::language_set::{ConstructId, Grammar, LanguageSet, NotationSet}; -pub use construct::{Arity, ArityType, Construct, Sort}; diff --git a/language/src/lib.rs b/language/src/lib.rs deleted file mode 100644 index a8b8be1..0000000 --- a/language/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod ast; -mod language; - -pub use crate::language::{Arity, ArityType, Construct, ConstructId, LanguageSet, Sort}; -pub use ast::{Ast, AstCase, AstForest, AstRef, FixedAst, ListyAst, Text, TextyAst}; diff --git a/old-stuff/doc/mod.rs b/old-stuff/doc/mod.rs deleted file mode 100644 index 1caaaa1..0000000 --- a/old-stuff/doc/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Construct and edit documents. - -mod cursor; - -pub use doc::cursor::{Cursor, Mode}; -pub use tree::{Node, Tree, TreeRef, TreeMut}; - diff --git a/old-stuff/editor/command.rs b/old-stuff/editor/command.rs deleted file mode 100644 index 268eeb1..0000000 --- a/old-stuff/editor/command.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::fmt; - -use self::Command::*; - - -/// An editor command that can be performed by the Cursor. -#[derive(Clone)] -pub enum Command { - // Tree Navigation - Right, Left, Up, Down, - // Text Navigation - RightChar, LeftChar, - // Modes - EnterText, ExitText, - // Tree Editing - AddChild, DeleteTree, ReplaceTree(String), - // Text Editing - InsertChar(char), DeleteChar -} - -impl fmt::Display for Command { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let name = match self { - // Tree Navigation - &Right => "Right", - &Left => "Left", - &Up => "Up", - &Down => "Down", - // Text Navigation - &RightChar => "RightChar", - &LeftChar => "LeftChar", - // Modes - &EnterText => "EnterText", - &ExitText => "ExitText", - // Tree Editing - &AddChild => "AddChild", - &DeleteTree => "DeleteTree", - &ReplaceTree(_) => "ReplaceTree", - // Text Editing - &InsertChar(_) => "InsertChar", - &DeleteChar => "DeleteChar" - }; - write!(f, "{}", name) - } -} diff --git a/old-stuff/editor/editor.rs b/old-stuff/editor/editor.rs deleted file mode 100644 index c9b3b8a..0000000 --- a/old-stuff/editor/editor.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! The Synless tree editor itself (the terminal application). - -use geometry::*; -use tree::{Tree, Cursor}; -use style::{Color, Style, ColorTheme}; -use terminal::{Terminal, Key}; -use terminal::Event::{MouseEvent, KeyEvent}; -use render::render; -use language::Language; -use editor::keymap::KeyMap; -use editor::keymap::Action; -use editor::command::Command; -use editor::command::Command::*; - - -const CENTERLINE: f32 = 0.3; - -const RAINBOW: [Color; 6] = [ - Color::Red, Color::Yellow, Color::Green, - Color::Cyan, Color::Blue, Color::Magenta]; - -pub struct Editor<'t, 'l : 't> { - terminal: Terminal, - language: &'l Language, - keymap: KeyMap, - cursor: Cursor<'t, 'l>, - keygroup: Option -} - -impl<'t, 'l> Editor<'t, 'l> { - /// Construct a new Synless tree editor. - pub fn new(language: &'l Language, - keymap: KeyMap, - theme: ColorTheme, - tree: &'t mut Tree<'l>) - -> Editor<'t, 'l> - { - Editor{ - terminal: Terminal::new(theme), - language: language, - keymap: keymap, - cursor: Cursor::new(tree), - keygroup: None - } - } - - /// Run the editor. - pub fn run(&mut self) { - self.display(); - loop { - match (&self).terminal.poll_event() { - None => (), - Some(MouseEvent(pos)) => { - self.clear(); - self.press_mouse(pos); - self.display(); - } - Some(KeyEvent(Key::Esc)) => { - break; - } - Some(KeyEvent(key)) => { - self.clear(); - self.press_key(key); - self.display(); - } - } - } - } - - fn clear(&mut self) { - self.terminal.clear(); - } - - fn rainbow(&mut self) { - for i in 0..7 { - let color = RAINBOW[i % 6]; - let pos = Pos{ row: 41 as Row, col: 5 * i as Col }; - self.terminal.print_str("Color", pos, Style::color(color)); - let pos = Pos{ row: 42 as Row, col: 5 * i as Col }; - self.terminal.print_str("Color", pos, Style::reverse_color(color)); - } - } - - fn display(&mut self) { - render(self.cursor.as_ref(), - self.cursor.char_index(), - &mut self.terminal, - CENTERLINE); -// self.background(); - self.rainbow(); - self.terminal.present(); - } - - fn press_mouse(&mut self, pos: Pos) { - self.terminal.simple_print(&format!("{:?} {:?}", pos.col, pos.row), - Pos{ row: 40, col: 1 }); - } - - fn perform(&mut self, command: &Command) { - debug!("Command: {}", command); - match command { - // Tree Navigation - &Right => { self.cursor.right(); }, - &Left => { self.cursor.left(); }, - &Up => { self.cursor.up(); }, - &Down => { self.cursor.down(); }, - // Text Navigation - &RightChar => { self.cursor.right_char(); }, - &LeftChar => { self.cursor.left_char(); }, - // Modes - &EnterText => { self.cursor.enter_text(); }, - &ExitText => { self.cursor.exit_text(); }, - // Tree Editing - &AddChild => { self.cursor.add_child(); }, - &DeleteTree => { self.cursor.delete_tree(); }, - &ReplaceTree(ref name) => { - let con = self.language.lookup_name(name); - self.cursor.replace_tree(con); - } - // Text Editing - &InsertChar(ch) => { self.cursor.insert_char(ch); }, - &DeleteChar => { self.cursor.delete_char(); } - } - } - - fn get_keymap(&self) -> &KeyMap { - match self.keygroup { - None => &self.keymap, - Some(ref group) => self.keymap.lookup_keygroup(group) - } - } - - fn lookup_key(&self, key: Key) -> Option { - let keymap = self.get_keymap(); - keymap.lookup(key, self.cursor.mode()) - } - - fn press_key(&mut self, key: Key) { - if let Some(action) = self.lookup_key(key) { - self.keygroup = None; - match action { - Action::Command(cmd) => self.perform(&cmd), - Action::KeyGroup(group) => { - debug!("Entering key group {}", group); - self.keygroup = Some(group); - } - } - } - self.terminal.simple_print(&format!("{:?}", key), - Pos{ row: 40, col: 1 }); - self.terminal.simple_print(&format!("{:?}", self.cursor.path()), - Pos{ row: 40, col: 20 }); - self.terminal.simple_print(&format!("{}", self.cursor.mode()), - Pos{ row: 40, col: 40}); - if let &Some(ref group) = &self.keygroup { - self.terminal.simple_print(group, - Pos{ row: 40, col: 50}); - } - } -} - - -/* -pub type Action = Box ()>; - -pub type KeyMap = HashMap>; -*/ diff --git a/old-stuff/editor/keymap.rs b/old-stuff/editor/keymap.rs deleted file mode 100644 index c9f1d59..0000000 --- a/old-stuff/editor/keymap.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::collections::HashMap; - -use terminal::Key; -use editor::command::Command; -use tree::Mode::{self, TreeMode, TextMode}; -use language::Language; - - -/// A set of keyboard bindings. -pub struct KeyMap { - tree_map: HashMap, - text_map: HashMap, - groups: HashMap -} - -/// A group of keyboard bindings: several key bindings that can be -/// accessed by first pressing a shared common key. -struct KeyGroup { - keymap: KeyMap -} - -/// An action to perform on a key press. -#[derive(Clone)] -pub enum Action { - /// Perform the builtin command. - Command(Command), - /// Enter a subgroup of keyboard bindings. - KeyGroup(String) -} - -impl KeyMap { - pub fn new() -> KeyMap { - KeyMap{ - tree_map: HashMap::new(), - text_map: HashMap::new(), - groups: HashMap::new() - } - } - - /// Lookup what should happen when a key is pressed. - pub fn lookup(&self, key: Key, mode: Mode) -> Option { - if let (TextMode, Key::Char(key)) = (mode, key) { - return Some(Action::Command(Command::InsertChar(key))) - } - let map = match mode { - TreeMode => &self.tree_map, - TextMode => &self.text_map - }; - match map.get(&key) { - None => None, - Some(action) => Some(action.clone()) - } - } - - /// Lookup the KeyMap for a `KeyGroup(name)` action. - pub fn lookup_keygroup(&self, name: &str) -> &KeyMap { - match self.groups.get(name) { - Some(keygroup) => &keygroup.keymap, - None => panic!("Key group not found.") - } - } - - /// Add a new text mode key binding, that performs a builtin command. - pub fn add_text_cmd(&mut self, key: Key, cmd: Command) { - self.text_map.insert(key, Action::Command(cmd)); - } - - /// Add a new tree mode key binding, that performs a builtin command. - pub fn add_tree_cmd(&mut self, key: Key, cmd: Command) { - self.tree_map.insert(key, Action::Command(cmd)); - } - - /// Add a grouped set of key bindings for text mode. - pub fn add_text_keygroup(&mut self, key: Key, name: &str, keymap: KeyMap) { - let group = KeyGroup{ - keymap: keymap - }; - let action = Action::KeyGroup(name.to_string()); - - self.groups.insert(name.to_string(), group); - self.text_map.insert(key, action); - } - - /// Add a grouped set of key bindings for tree mode. - pub fn add_tree_keygroup(&mut self, key: Key, name: &str, keymap: KeyMap) { - let group = KeyGroup{ - keymap: keymap - }; - let action = Action::KeyGroup(name.to_string()); - - self.groups.insert(name.to_string(), group); - self.tree_map.insert(key, action); - } - - /// A keymap for `Language::example_language()`. - pub fn example_keymap(lang: &Language) -> KeyMap { - let mut map = KeyMap::new(); - - // Tree Navigation - map.add_tree_cmd(Key::Left, Command::Left); - map.add_tree_cmd(Key::Right, Command::Right); - map.add_tree_cmd(Key::Up, Command::Up); - map.add_tree_cmd(Key::Down, Command::Down); - map.add_tree_cmd(Key::Char('h'), Command::Left); - map.add_tree_cmd(Key::Char('j'), Command::Down); - map.add_tree_cmd(Key::Char('k'), Command::Up); - map.add_tree_cmd(Key::Char('l'), Command::Right); - // Text Navigation - map.add_text_cmd(Key::Left, Command::LeftChar); - map.add_text_cmd(Key::Right, Command::RightChar); - // Modes - map.add_tree_cmd(Key::Enter, Command::EnterText); - map.add_text_cmd(Key::Enter, Command::ExitText); - // Tree Editing - map.add_tree_cmd(Key::Char('a'), Command::AddChild); - map.add_tree_cmd(Key::Backspace, Command::DeleteTree); - // Text Editing - map.add_text_cmd(Key::Backspace, Command::DeleteChar); - - let mut lang_keymap = KeyMap::new(); - for (&key, name) in &lang.keymap { - let cmd = Command::ReplaceTree(name.to_string()); - lang_keymap.add_tree_cmd(Key::Char(key), cmd); - } - map.add_tree_keygroup(Key::Char('i'), "insert", lang_keymap); - - map - } -} diff --git a/old-stuff/editor/mod.rs b/old-stuff/editor/mod.rs deleted file mode 100644 index eb591df..0000000 --- a/old-stuff/editor/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! The Synless tree editor. - -mod command; -mod keymap; -mod editor; - -pub use editor::command::Command; -pub use editor::keymap::KeyMap; -pub use editor::editor::Editor; diff --git a/old-stuff/main.rs b/old-stuff/main.rs deleted file mode 100644 index a865020..0000000 --- a/old-stuff/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate synless; - -fn main() { - println!("ok"); -} -/* -use synless::ColorTheme; -use synless::Tree; -use synless::Language; -use synless::KeyMap; -use synless::Editor; - -fn main() { - let language = Language::example_language(); - let keymap = KeyMap::example_keymap(&language); - let theme = ColorTheme::colorful_hexagon(); - let mut tree = Tree::new_hole(); - let mut editor = Editor::new(&language, keymap, theme, &mut tree); - editor.run(); -} -*/ diff --git a/old-stuff/render/locate_cursor.rs b/old-stuff/render/locate_cursor.rs deleted file mode 100644 index e4b2564..0000000 --- a/old-stuff/render/locate_cursor.rs +++ /dev/null @@ -1,118 +0,0 @@ -use geometry::*; -use tree::{Path, TreeRef, match_end_of_path}; -use syntax::LayoutRegion; -use syntax::Layout::*; - - -// Step 1: figure out where the cursor is in doc coords - -pub(in render) struct CursorLocator { - cursor_path: Path -} - -// Err means "found"; Ok means "not yet found" -type Res = Result<(), Region>; - -impl CursorLocator { - pub(in render) fn locate(doc: TreeRef, doc_region: Region) -> Region { - let mut locator = CursorLocator{ - cursor_path: doc.path().clone() - }; - match locator.loc_tree(doc.root(), doc_region) { - // Err means "found"; Ok means "not yet found" - Ok(()) => err_cursor_not_found(), - Err(region) => region - } - } - - fn loc_tree(&mut self, tree: TreeRef, region: Region) -> Res { - if tree.path() == &self.cursor_path { - Err(tree.lay_out(region).region) - } else if self.cursor_path.starts_with(tree.path()) { - let layout = tree.lay_out(region); - self.loc_layout(tree, layout) - } else { - Ok(()) - } - } - - fn loc_layout(&mut self, tree: TreeRef, layout: LayoutRegion) -> Res { - match layout.layout { - Literal(_, _) => Ok(()), - Text(_) => - match match_end_of_path(&self.cursor_path, tree.path()) { - Some(i) => { - let pos = layout.region.beginning() - + Pos{ row: 0, col: i as Col }; - Err(Region::char_region(pos)) - } - None => Ok(()) - } - Flush(box layout) => - self.loc_layout(tree, layout), - Child(index) => - self.loc_tree(tree.child(index), layout.region), - Concat(box layout1, box layout2) => { - self.loc_layout(tree.clone(), layout1)?; - self.loc_layout(tree, layout2) - } - } - } -} - -fn err_cursor_not_found() -> ! { - panic!("render: Selection not found!") -} - -#[cfg(test)] -mod tests { - use super::*; - use language::Language; - use language::make_example_tree; - - #[test] - fn test_locate_cursor() { - let lang = Language::example_language(); - let doc = make_example_tree(&lang, true); - - let width = 17; - // The rendering: -"func foo(abc, - def) { - 'abcdef' - + 'abc' -}"; - - let region = Region{ - pos: Pos::zero(), - bound: Bound::infinite_scroll(width) - }; - let tree = doc.as_ref().child(2).child(0); - let expected = Region{ - pos: Pos{ row: 2, col: 2 }, - bound: Bound{ height: 0, width: 8, indent: 8 } - }; - assert_eq!(CursorLocator::locate(tree, region), expected); - - let tree = doc.as_ref().child(2); - let expected = Region{ - pos: Pos{ row: 2, col: 2 }, - bound: Bound{ height: 1, width: 8, indent: 7 } - }; - assert_eq!(CursorLocator::locate(tree, region), expected); - - let tree = doc.as_ref(); - let expected = Region{ - pos: Pos{ row: 0, col: 0 }, - bound: Bound{ height: 4, width: 15, indent: 1 } - }; - assert_eq!(CursorLocator::locate(tree, region), expected); - - let tree = doc.as_ref().child(1); - let expected = Region{ - pos: Pos{ row: 0, col: 9 }, - bound: Bound{ height: 1, width: 4, indent: 3 } - }; - assert_eq!(CursorLocator::locate(tree, region), expected); - } -} diff --git a/old-stuff/render/mod.rs b/old-stuff/render/mod.rs deleted file mode 100644 index f12ce80..0000000 --- a/old-stuff/render/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Render the tree onto the screen. - -mod write; -mod locate_cursor; -mod position_screen; -mod render; - -use geometry::*; -use syntax::Bound; -use render::locate_cursor::CursorLocator; -use render::position_screen::position_screen; -use render::render::Renderer; - -use tree::TreeRef; -use terminal::Terminal; - -/// Render the tree onto the screen. -pub fn render(tree: TreeRef, - char_index: Option, - terminal: &mut Terminal, - center: f32) -{ - // We will render the document onto an infinitely long scroll - let region = Region{ - pos: Pos::zero(), - bound: Bound::infinite_scroll(terminal.size().col) - }; - - // Step 1: figure out where the cursor is in doc coords - let cursor_region = CursorLocator::locate(tree.clone(), region); - - // Step 2: determine where the screen should be in doc coords - let screen_size = Bound{ - width: terminal.size().col, - height: terminal.size().row, - indent: terminal.size().col - }; - let screen_region = position_screen(screen_size, cursor_region, center); - - // Step 3: render the doc onto the screen - Renderer::render(terminal, screen_region, tree, char_index, region); -} diff --git a/old-stuff/render/position_screen.rs b/old-stuff/render/position_screen.rs deleted file mode 100644 index 1307170..0000000 --- a/old-stuff/render/position_screen.rs +++ /dev/null @@ -1,79 +0,0 @@ -use geometry::*; - -// Step 2: determine where the screen should be in doc coords - -/// Position the screen on the document. -/// `screen_bound` is the size of the screen, -/// in terminal coordinates. -/// `cursor_region` is the region the cursor takes up, -/// in document coordinates. -/// `center` is the row the cursor should be centered at, -/// as a fraction of the screen height (0 is the top). -pub(in render) fn position_screen( - screen_bound: Bound, cursor_region: Region, center: f32) - -> Region -{ - // Number of rows from the top of the screen to the centerline. - let centerline = (center * screen_bound.height as f32) as Row; - let row = - if centerline >= cursor_region.beginning().row { - // We're at the very beginning of the document. - 0 - } else { - cursor_region.beginning().row - centerline - }; - Region{ - pos: Pos{ - row: row, - col: 0 - }, - bound: screen_bound - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_position_screen() { - let cursor_bound = Bound{ width: 2, height: 2, indent: 1}; - - let screen_bound = Bound{ width: 5, height: 2, indent: 3 }; - let cursor_region = Region{ - pos: Pos{ row: 1, col: 0 }, - bound: cursor_bound - }; - let center = 0.0; - assert_eq!(position_screen(screen_bound, cursor_region, center), - Region{ - pos: Pos{ row: 1, col: 0 }, - bound: screen_bound - }); - - let screen_bound = Bound{ width: 5, height: 2, indent: 3 }; - let cursor_region = Region{ - pos: Pos{ row: 1, col: 0 }, - bound: cursor_bound - }; - let center = 0.5; - assert_eq!(position_screen(screen_bound, cursor_region, center), - Region{ - pos: Pos{ row: 0, col: 0 }, - bound: screen_bound - }); - - let screen_bound = Bound{ width: 5, height: 2, indent: 3 }; - let cursor_region = Region{ - pos: Pos{ row: 20, col: 20 }, - bound: cursor_bound - }; - let center = 0.5; - assert_eq!(position_screen(screen_bound, cursor_region, center), - Region{ - pos: Pos{ row: 19, col: 0 }, - bound: screen_bound - }); - } -} diff --git a/old-stuff/render/render.rs b/old-stuff/render/render.rs deleted file mode 100644 index 475c72d..0000000 --- a/old-stuff/render/render.rs +++ /dev/null @@ -1,125 +0,0 @@ -extern crate rustbox; - -use std::iter::Iterator; - -use geometry::*; -use tree::{Path, extend_path, match_end_of_path, TreeRef}; -use syntax::LayoutRegion; -use syntax::Layout::*; -use style::{Style, Shade}; -use terminal::{Terminal}; - - -// Step 3: render the doc onto the screen - -pub(in render) struct Renderer<'a> { - terminal: &'a mut Terminal, - screen: Region, - cursor_path: Path, - shading: Vec<(Region, Shade)> -} - -impl<'a> Renderer<'a> { - pub(in render) fn render(terminal: &'a mut Terminal, - screen: Region, - tree: TreeRef, - char_index: Option, - doc_region: Region) { - let cursor_path = match char_index { - None => tree.path().clone(), - Some(i) => extend_path(tree.path(), i) - }; - let mut renderer = Renderer{ - terminal: terminal, - screen: screen, - cursor_path: cursor_path, - shading: vec!() - }; - renderer.render_tree(tree.root(), doc_region); - } - - fn render_tree(&mut self, tree: TreeRef, region: Region) { - // Lay it out - let layout = tree.lay_out(region); - let region = layout.region; - // Check if there's anything to show at all - if !region.overlaps(self.screen) { - return; - } - // Shade the background - let shade = self.shading_depth(tree.path()); - self.shade_region(region, shade); - // Render it - self.render_layout(tree, layout); - } - - fn render_layout(&mut self, tree: TreeRef, layout: LayoutRegion) { - match layout.layout { - Literal(text, style) => { - let pos = layout.region.beginning(); - self.render_str(text.chars(), pos, style); - } - Text(style) => { - let pos = layout.region.beginning(); - self.highlight_selected_char(pos, tree.path()); - self.render_str(tree.text().chars(), pos, style); - } - Flush(box layout) => { - self.render_layout(tree, layout); - } - Child(index) => { - let child = tree.child(index); - self.render_tree(child, layout.region); - } - Concat(box layout1, box layout2) => { - self.render_layout(tree.clone(), layout1); - self.render_layout(tree, layout2); - } - } - } - - fn render_str(&mut self, text: Text, pos: Pos, style: Style) - where Text : Iterator - { - for (i, ch) in text.enumerate() { - let pos = pos + Pos{ col: i as Col, row: 0 }; - self.render_char(ch, pos, style); - } - } - - fn render_char(&mut self, ch: char, pos: Pos, mut style: Style) { - let pos = self.screen.transform(pos) - .expect("render: Text out of bounds"); - for &(ref region, ref shade) in &self.shading { - if region.contains(pos) { - style.shade = *shade; - } - } - if style.shade == Shade(0) { - style.emph.bold = true; - } - self.terminal.print_char(ch, pos, style); - } - - fn highlight_selected_char(&mut self, pos: Pos, path: &Path) { - match match_end_of_path(&self.cursor_path, path) { - None => (), - Some(i) => { - let pos = pos + Pos{ row: 0, col: i as Col }; - let region = Region::char_region(pos); - self.shade_region(region, Shade(0)); - } - } - } - - fn shading_depth(&self, path: &Path,) -> Shade { - // Shading is len(selected) - len(common_prefix) - let prefix_len = common_prefix_len(path, &self.cursor_path); - Shade(self.cursor_path.len() - prefix_len) - } - - fn shade_region(&mut self, region: Region, shade: Shade) { - self.terminal.shade_region(region, shade); - self.shading.push((region, shade)); - } -} diff --git a/old-stuff/render/write.rs b/old-stuff/render/write.rs deleted file mode 100644 index 2d78046..0000000 --- a/old-stuff/render/write.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::fmt; -use std::fmt::Write; - -use geometry::*; -use tree::TreeRef; -use syntax::{Bound, LayoutRegion}; -use syntax::Layout::*; - - -impl<'l, 't> TreeRef<'l, 't> { - pub fn write(self, width: Col) -> String { - let mut s = String::new(); - write_doc(&mut s, self, width).unwrap(); - s - } -} -// (tested in syntax/mod.rs) - -fn write_doc(f: &mut Write, doc: TreeRef, width: Col) -> fmt::Result { - let bound = Bound::infinite_scroll(width); - let region = Region{ bound: bound, pos: Pos::zero() }; - write_tree(f, doc, region) -} - -fn write_tree(f: &mut Write, tree: TreeRef, region: Region) -> fmt::Result { - let layout = tree.lay_out(region); - write_layout(f, tree, layout) -} - -fn write_layout(f: &mut Write, tree: TreeRef, lay: LayoutRegion) -> fmt::Result { - match lay.layout { - Literal(s, _) => { - write!(f, "{}", s) - } - Text(_) => { - write!(f, "{}", tree.text()) - } - Flush(box inner_lay) => { - write_layout(f, tree, inner_lay)?; - write!(f, "\n")?; - let indent = lay.region.beginning().col; - write!(f, "{}", " ".repeat(indent as usize)) - } - Child(index) => { - write_tree(f, tree.child(index), lay.region) - } - Concat(box layout1, box layout2) => { - write_layout(f, tree.clone(), layout1)?; - write_layout(f, tree, layout2) - } - } -} diff --git a/old-stuff/tree/cursor.rs b/old-stuff/tree/cursor.rs deleted file mode 100644 index e63abbf..0000000 --- a/old-stuff/tree/cursor.rs +++ /dev/null @@ -1,491 +0,0 @@ -use std::fmt; - -use construct::Construct; -use tree::{Path, Tree, TreeRef, TreeMut}; - -use self::Mode::*; - -/// Indicates whether the cursor is currently editing a tree or text. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Mode { - TreeMode, - TextMode -} - -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &TreeMode => write!(f, "Tree"), - &TextMode => write!(f, "Text") - } - } -} - -/// The cursor navigates and edits a document. -pub struct Cursor<'t, 'l : 't> { - char_index: Option, - tree: TreeMut<'t, 'l> -} - -impl<'t, 'l : 't> Cursor<'t, 'l> { - - pub fn new(tree: &'t mut Tree<'l>) -> Cursor<'t, 'l> { - Cursor{ - char_index: None, - tree: tree.as_mut() - } - } - - // Info // - - /// The root of the document. - pub fn root(&'t self) -> TreeRef<'t, 'l> { - self.tree.as_ref().root() - } - - /// The tree at the current cursor location. - pub fn as_ref(&'t self) -> TreeRef<'t, 'l> { - self.tree.as_ref() - } - - /// The current cursor location. - pub fn path(&self) -> Path { - self.tree.path().clone() - } - - /// If editing text, the character index, else None. - pub fn char_index(&self) -> Option { - self.char_index - } - - // Tree Navigation // - - /// Attempt to move right (i.e., to the next tree sibling). - /// - /// This will fail if the cursor is in text mode, - /// or if there is no right sibling. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn right(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.has_parent() { - let i = self.tree.index(); - if i + 1 < self.tree.num_siblings() { - self.tree.goto_parent(); - self.tree.goto_child(i + 1); - return true; - } - } - false - } - - /// Attempt to move left (i.e., to the previous tree sibling). - /// - /// This will fail if the cursor is in text mode, - /// or if there is no left sibling. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn left(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.has_parent() { - let i = self.tree.index(); - if i > 0 { - self.tree.goto_parent(); - self.tree.goto_child(i - 1); - return true; - } - } - false - } - - /// Attempt to move up (i.e., to the parent node). - /// - /// This will fail if the cursor is in text mode, - /// or if it is at the root of the document. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn up(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.has_parent() { - let index = self.tree.goto_parent(); - *self.tree.breadcrumb_mut() = index; - return true; - } - false - } - - /// Attempt to move down (i.e., to a child). - /// - /// This will go to the child that was last visited. - /// It will fail if the cursor is in text mode, - /// or if it is at a tree that has no children. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn down(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.is_foresty() - && self.tree.num_children() > 0 - { - let index = self.tree.breadcrumb(); - self.tree.goto_child(index); - return true; - } - false - } - - // Text Navigation // - - /// Attempt to move right one character. - /// - /// This will fail if the cursor is in tree mode, - /// or if it is at the end of the text. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn right_char(&mut self) -> bool { - if let Some(i) = self.char_index { - let text = self.tree.text(); - if i < text.chars().count() { - self.char_index = Some(i + 1); - return true; - } - } - false - } - - /// Attempt to move left one character. - /// - /// This will fail if the cursor is in tree mode, - /// or if it is at the beginning of the text. - /// - /// Returns `true` if movement was successful, or `false` otherwise. - pub fn left_char(&mut self) -> bool { - if let Some(i) = self.char_index { - if i > 0 { - self.char_index = Some(i - 1); - return true; - } - } - false - } - - // Modes // - - /// Is the cursor currently selecting a tree, or text? - pub fn mode(&self) -> Mode { - if self.char_index.is_some() { - TextMode - } else { - TreeMode - } - } - - /// Attempt to enter text mode. - /// - /// This will go to the last selected character position in the text - /// (or the end if it has not been selected yet). - /// It will fail if the cursor is already in text mode, - /// or if the tree selected is not texty. - /// - /// Returns `true` if the mode change was successful, or `false` otherwise. - pub fn enter_text(&mut self) -> bool { - if self.mode() == TreeMode && self.tree.is_texty() { - self.char_index = Some(self.tree.breadcrumb()); - return true; - } - false - } - - /// Attempt to exit text mode. - /// - /// This will fail if the cursor is already in tree mode. - /// - /// Returns `true` if the mode change was successful, or `false` otherwise. - pub fn exit_text(&mut self) -> bool { - if self.mode() == TextMode { - *self.tree.breadcrumb_mut() = self.char_index.unwrap(); - self.char_index = None; - return true; - } - false - } - - // Tree Operations - - /// Add a child to the end of an extendable tree. - /// - /// This will fail if the cursor is in text mode. - /// - /// Returns `true` if successful. - pub fn add_child(&mut self) -> bool { - if self.tree.is_foresty() && self.tree.node().is_extendable() { - self.tree.forest_mut().push(Tree::new_hole()); - self.update(); - return true; - } - false - } - - /// Replace the selected tree with a new empty tree. - /// - /// This will fail if the cursor is in text mode. - /// - /// Returns the tree that was replaced. - pub fn replace_tree(&mut self, construct: &'l Construct) -> Option> { - if self.mode() == TreeMode { - let tree = self.tree.replace(Tree::new(construct)); - self.update(); - return Some(tree); - } - None - } - - /// Delete the selected tree. - /// - /// This will fail if the cursor is in text mode. - /// - /// Returns the tree that was deleted. - pub fn delete_tree(&mut self) -> Option> { - if self.mode() == TreeMode { - let tree = self.tree.replace(Tree::new_hole()); - self.update(); - return Some(tree); - } - None - } - - // Text Operations - - /// Insert a character after the selected position. - /// - /// This will fail if the cursor is in tree mode. - /// - /// Returns `true` if successful. - pub fn insert_char(&mut self, ch: char) -> bool { - if let Some(i) = self.char_index { - self.tree.text_mut().insert(i, ch); - self.char_index = Some(i + 1); - self.update(); - return true; - } - false - } - - /// Delete the character before the selected position. - /// - /// This will fail if the cursor is in tree mode. - /// - /// Returns the deleted character. - pub fn delete_char(&mut self) -> Option { - if let Some(i) = self.char_index { - if i >= 1 { - let ch = self.tree.text_mut().remove(i - 1); - self.char_index = Some(i - 1); - self.update(); - return Some(ch) - } - } - None - } - - ///////////// - // PRIVATE // - ///////////// - - fn update(&mut self) { - self.tree.update(); - } -} - - -#[cfg(test)] -mod test { - use super::*; - use construct::{TEST_TEXT, TEST_FOREST}; - use tree::Tree; - - fn t(children: Vec>) -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, children) - } - - fn bt(k: usize) -> Tree<'static> { - // What happens when you can make a doorway that - // connects the universe to a copy of itself, - // and then one of you makes n doorways? - t((0..k).map(|i| bt(i)).collect()) - } - - fn tt() -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "hi"), - Tree::new_text(&TEST_TEXT, "hey"))) - } - - #[test] - fn test_tree_navigation() { - let mut tree = bt(3); - let mut doc = Cursor::new(&mut tree); - - assert_eq!(doc.path(), vec!()); - // Down - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(0)); - // Down too far - assert_eq!(doc.down(), false); - assert_eq!(doc.path(), vec!(0)); - // Right - assert_eq!(doc.right(), true); - assert_eq!(doc.path(), vec!(1)); - // Right too far - assert_eq!(doc.right(), true); - assert_eq!(doc.right(), false); - assert_eq!(doc.path(), vec!(2)); - // Left - assert_eq!(doc.left(), true); - assert_eq!(doc.path(), vec!(1)); - // Left too far - assert_eq!(doc.left(), true); - assert_eq!(doc.left(), false); - assert_eq!(doc.path(), vec!(0)); - // Something to remember - assert_eq!(doc.right(), true); - assert_eq!(doc.right(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 0)); - assert_eq!(doc.right(), true); - assert_eq!(doc.path(), vec!(2, 1)); - // Up - assert_eq!(doc.up(), true); - assert_eq!(doc.path(), vec!(2)); - assert_eq!(doc.up(), true); - assert_eq!(doc.path(), vec!()); - // Up too far - assert_eq!(doc.up(), false); - assert_eq!(doc.path(), vec!()); - // Direct memory - assert_eq!(doc.down(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 1)); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 1, 0)); - // Indirect memory - assert_eq!(doc.up(), true); - assert_eq!(doc.up(), true); - assert_eq!(doc.left(), true); - assert_eq!(doc.up(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(1, 0)); - assert_eq!(doc.up(), true); - assert_eq!(doc.right(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.down(), true); - assert_eq!(doc.path(), vec!(2, 1, 0)); - } - - #[test] - fn test_text_navigation() { - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - - // Cannot enter text - assert_eq!(doc.mode(), TreeMode); - assert_eq!(doc.enter_text(), false); - assert_eq!(doc.mode(), TreeMode); - assert_eq!(doc.char_index(), None); - - // Can enter text - assert_eq!(doc.down(), true); - assert_eq!(doc.right(), true); - assert_eq!(doc.char_index(), None); - assert_eq!(doc.enter_text(), true); - assert_eq!(doc.mode(), TextMode); - assert_eq!(doc.char_index(), Some(3)); - - // Can navigate - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), false); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.right_char(), true); - assert_eq!(doc.right_char(), false); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.left_char(), true); - assert_eq!(doc.char_index(), Some(1)); - - // Can exit text - assert_eq!(doc.exit_text(), true); - assert_eq!(doc.mode(), TreeMode); - assert_eq!(doc.char_index(), None); - - // Character position is remembered - assert_eq!(doc.enter_text(), true); - assert_eq!(doc.char_index(), Some(1)); - - // Character position is not infectious - assert_eq!(doc.exit_text(), true); - assert_eq!(doc.left(), true); - assert_eq!(doc.enter_text(), true); - assert_eq!(doc.char_index(), Some(2)); - } - - #[test] - fn test_text_ops() { - // Tests for: insert char, delete char - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - - doc.down(); - doc.right(); - doc.enter_text(); - assert_eq!(doc.delete_char(), Some('y')); - doc.left_char(); - doc.left_char(); - assert_eq!(doc.delete_char(), None); - assert_eq!(doc.insert_char('l'), true); - doc.right_char(); - doc.right_char(); - assert_eq!(doc.insert_char('r'), true); - doc.exit_text(); - assert_eq!(doc.as_ref().text(), "lher"); - } - - #[test] - fn test_tree_ops() { - // Tests for: add child, replace tree, delete tree - let mut tree = bt(3); - let mut doc = Cursor::new(&mut tree); - - assert_eq!(doc.add_child(), true); - doc.down(); - doc.right(); - doc.right(); - doc.right(); - assert_eq!(doc.as_ref().len(), 0); - doc.left(); - let opt_tree = doc.delete_tree(); - assert!(opt_tree.is_some()); - assert_eq!(opt_tree.unwrap().len(), 2); - assert_eq!(doc.as_ref().len(), 0); - doc.left(); - let opt_tree = doc.replace_tree(&TEST_TEXT); - assert!(opt_tree.is_some()); - assert_eq!(opt_tree.unwrap().len(), 1); - assert_eq!(&doc.as_ref().node().construct().name, "TEST_TEXT"); - - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - doc.down(); - assert_eq!(doc.add_child(), false); - } - - #[test] - fn test_as_ref() { - let mut tree = tt(); - let mut doc = Cursor::new(&mut tree); - - doc.down(); - doc.right(); - assert_eq!(doc.root().path(), &vec!()); - assert_eq!(doc.as_ref().path(), &vec!(1)); - } -} diff --git a/old-stuff/tree/mod.rs b/old-stuff/tree/mod.rs deleted file mode 100644 index b0c04f9..0000000 --- a/old-stuff/tree/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Synless is a tree editor. Here are the trees. -mod path; -mod node; -mod tree; -mod tree_ref; -mod tree_mut; -mod cursor; - -pub use tree::node::Node; -pub use tree::tree_ref::TreeRef; -pub use tree::tree_mut::TreeMut; -pub use tree::tree::{Tree}; -pub use tree::path::{Path, extend_path, pop_path, match_end_of_path}; -pub use tree::cursor::{Cursor, Mode}; diff --git a/old-stuff/tree/node.rs b/old-stuff/tree/node.rs deleted file mode 100644 index 6424698..0000000 --- a/old-stuff/tree/node.rs +++ /dev/null @@ -1,110 +0,0 @@ -use syntax::BoundSet; -use construct::{Construct, HOLE}; -use construct::Arity::*; - -/// A node of a tree. -#[derive(Debug)] -pub struct Node<'l> { - /// The syntactic construct that this is an instance of. - pub(in tree) construct: &'l Construct, - /// The last child to have been visited. - pub(in tree) breadcrumb: usize, - /// The bounds in which this tree fits, remembered for efficiency. - pub(in tree) bounds: BoundSet -} -// (tested in tree/bounds.rs) - -impl<'l> Node<'l> { - - // Public // - - pub fn is_extendable(&self) -> bool { - self.construct.is_extendable() - } - - pub fn construct(&self) -> &'l Construct { - &self.construct - } - - // Constructors // - - pub(in tree) fn new_forest(construct: &'l Construct, - children: Vec<&Node>) -> Node<'l> - { - let mut node = Node::new(construct); - node.update_forest(children); - node - } - - pub(in tree) fn new_text(construct: &'l Construct, - text: &str) -> Node<'l> - { - let mut node = Node::new(construct); - node.update_text(text); - node.breadcrumb = text.chars().count(); - node - } - - pub(in tree) fn new_hole() -> Node<'l> { - let mut node = Node::new(&HOLE); - node.update_forest(vec!()); - node - } - - // MUST be initialized with `update` before being used. - fn new(construct: &'l Construct) -> Node<'l> { - Node{ - construct: construct, - breadcrumb: 0, - bounds: BoundSet::new() - } - } - - // Updating // - - pub(in tree) fn update_forest(&mut self, children: Vec<&Node>) { - match self.construct.arity { - ForestArity{..} => (), - _ => panic!("update_forest: called on a text node") - } - let child_bounds: Vec<&BoundSet> = children.into_iter().map(|n| &n.bounds).collect(); - let arity = self.construct.arity.arity(); - let bounds = self.construct.syntax.bound(arity, child_bounds, false); - - self.bounds = bounds; - } - - pub(in tree) fn update_text(&mut self, text: &str) { - match self.construct.arity { - TextArity => (), - _ => panic!("update_text: called on a tree node") - } - let text_bound = BoundSet::literal(text); - let arity = self.construct.arity.arity(); - let bounds = self.construct.syntax.bound( - arity, vec!(&text_bound), text.is_empty()); - - self.bounds = bounds; - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_node_construction() { - use syntax::Bound; - use language::make_example_tree; - use language::Language; - - let lang = Language::example_language(); - let tree = make_example_tree(&lang, false); - let bounds: Vec = tree.node.bounds.into_iter().collect(); - assert_eq!(bounds, vec!( - Bound{ width: 42, height: 0, indent: 42 }, - Bound{ width: 33, height: 1, indent: 33 }, - Bound{ width: 21, height: 2, indent: 1 }, - Bound{ width: 20, height: 3, indent: 1 }, - Bound{ width: 15, height: 4, indent: 1 }, - Bound{ width: 12, height: 5, indent: 1 })); - } -} diff --git a/old-stuff/tree/path.rs b/old-stuff/tree/path.rs deleted file mode 100644 index 5a10139..0000000 --- a/old-stuff/tree/path.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Fun idea: implement dense Paths: -// lookup x on t: -// if x = 0 { -// t -// } -// else { -// let x = x - 1; -// let n = t.num_children(); -// let (child, n) = (x % n, x / n); -// lookup x on t[child] -// (For more efficiency, align to power of 2.) - -/// A position in the document. -pub type Path = Vec; - -/// Construct a new path that points to the `extension`'th child of -/// `path`. -pub fn extend_path(path: &Path, extension: usize) -> Path { - let mut path = path.clone(); - path.push(extension); - path -} - -/// Construct a new path that points to the parent of `path`. -/// If `path` is to the root and has no parent, then return it -/// unchanged. -pub fn pop_path(path: &Path) -> Path { - let mut path = path.clone(); - path.pop(); - path -} - -/// Check if `path` is `other`, with one final index. -/// If so, return that index. -pub fn match_end_of_path(path: &Path, other: &Path) -> Option { - if path.starts_with(&other) && path.len() == other.len() + 1 { - Some(*path.last().unwrap()) - } else { - None - } -} - - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_paths() { - let path = vec!(0, 2, 1); - assert_eq!(extend_path(&path, 3), vec!(0, 2, 1, 3)); - assert_eq!(path, vec!(0, 2, 1)); - assert_eq!(pop_path(&path), vec!(0, 2)); - assert_eq!(path, vec!(0, 2, 1)); - } -} diff --git a/old-stuff/tree/tree.rs b/old-stuff/tree/tree.rs deleted file mode 100644 index fa754ff..0000000 --- a/old-stuff/tree/tree.rs +++ /dev/null @@ -1,339 +0,0 @@ -use std::ops::{Index, IndexMut}; - -use construct::Construct; -use tree::node::Node; -use tree::path::Path; -#[cfg(test)] -use construct::{TEST_FOREST, TEST_TEXT}; - -use self::Children::{ForestChildren, TextChildren}; - - -/// A document being edited, represented as a Tree. -/// Because how else would you represent it? -/// -/// Every tree is either *foresty*, if it has trees for children, -/// or *texty*, if it contains text. -#[derive(Debug)] -pub struct Tree<'l> { - pub node: Node<'l>, - children: Children<'l> -} - -#[derive(Debug)] -enum Children<'l> { - ForestChildren(Vec>), - TextChildren(String) // TODO: Allow Tree children interspersed -} - - -// Children // - -impl<'l> Children<'l> { - - // TODO: efficiency - // Returns apparent length: text has a phantom character at the end - fn len(&self) -> usize { - match self { - &ForestChildren(ref trees) => trees.len(), - &TextChildren(ref childs) => childs.chars().count() + 1 - } - } - - fn is_texty(&self) -> bool { - match self { - &ForestChildren(_) => false, - &TextChildren(_) => true - } - } - - fn is_foresty(&self) -> bool { - match self { - &ForestChildren(_) => true, - &TextChildren(_) => false - } - } -} - - -// Trees // - -impl<'l> Tree<'l> { - - // Tree Constructors - - /// Construct a new hole. - pub fn new_hole() -> Tree<'l> { - Tree{ - node: Node::new_hole(), - children: ForestChildren(vec!()) - } - } - - /// Construct a tree with empty children/text. - pub fn new(construct: &'l Construct) -> Tree<'l> { - if construct.arity.is_text() { - Tree::new_text(construct, "") - } else { - let arity = construct.arity.arity(); - let mut children = vec!(); - for _ in 0..arity { - children.push(Tree::new_hole()); - } - Tree::new_forest(construct, children) - } - } - - /// Construct a new foresty tree. - pub fn new_forest(construct: &'l Construct, children: Vec>) -> Tree<'l> { - let node = { - let child_nodes = children.iter().map(|t| &t.node).collect(); - Node::new_forest(construct, child_nodes) - }; - Tree{ - node: node, - children: ForestChildren(children) - } - } - - /// Construct a new texty tree. - pub fn new_text(construct: &'l Construct, text: &str) -> Tree<'l> { - let node = Node::new_text(construct, text); - Tree{ - node: node, - children: TextChildren(text.to_string()) - } - } - - /// *This must be called every time this tree or one of its - /// children is modifed, on this tree and every tree of to the root.* - pub fn local_update(&mut self) { - match &self.children { - &ForestChildren(ref forest) => { - let child_nodes = forest.into_iter().map(|n| &n.node).collect(); - self.node.update_forest(child_nodes); - } - &TextChildren(ref text) => self.node.update_text(text) - } - } - - // All Trees // - - /// The number of children of a foresty tree, - /// or the number of characters plus one of a texty tree. - /// (It is plus one because there is a phantom character at the - /// end of text to make it easier to edit.) - pub fn len(&self) -> usize { - self.children.len() - } - - // Texty Trees // - - /// Is this tree texty? - pub fn is_texty(&self) -> bool { - self.children.is_texty() - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text(&self) -> &String { - match &self.children { - &ForestChildren(_) => error_not_texty(), - &TextChildren(ref text) => text - } - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text_mut(&mut self) -> &mut String { - match &mut self.children { - &mut ForestChildren(_) => error_not_texty(), - &mut TextChildren(ref mut text) => text - } - } - - // Foresty Trees // - - /// Is this tree foresty? - pub fn is_foresty(&self) -> bool { - self.children.is_foresty() - } - - /// Return the children of a foresty tree. Panics on texty trees. - pub fn forest(&self) -> &Vec> { - match &self.children { - &ForestChildren(ref forest) => forest, - &TextChildren(_) => error_not_foresty() - } - } - - /// Return the children of a foresty tree. Panics on texty trees. - pub fn forest_mut(&mut self) -> &mut Vec> { - match &mut self.children { - &mut ForestChildren(ref mut forest) => forest, - &mut TextChildren(_) => error_not_foresty() - } - } -} - - -// Index // - -impl<'l> Index for Children<'l> { - type Output = Tree<'l>; - fn index(&self, index: usize) -> &Tree<'l> { - match self { - &ForestChildren(ref forest) => { - match forest.get(index) { - None => error_invalid_path(), - Some(tree) => tree - } - } - &TextChildren(_) => error_invalid_path() - } - } -} - -impl<'l> Index for Tree<'l> { - /// - type Output = Tree<'l>; - /// Get the i'th child, or error. - fn index(&self, i: usize) -> &Tree<'l> { - self.children.index(i) - } -} - -impl<'a, 'l> Index<&'a [usize]> for Tree<'l> { - /// - type Output = Tree<'l>; - /// Lookup the path (represented as a slice), or error. - fn index(&self, path: &[usize]) -> &Tree<'l> { - match path.split_first() { - None => self, - Some((&i, path)) => self[i].index(path) - } - } -} - -impl<'a, 'l> Index<&'a Path> for Tree<'l> { - /// - type Output = Tree<'l>; - /// Lookup the path, or error. - fn index(&self, path: &Path) -> &Tree<'l> { - self.index(path.as_slice()) - } -} - - -// Index Mut // - -impl<'l> IndexMut for Children<'l> { - fn index_mut(&mut self, i: usize) -> &mut Tree<'l> { - match self { - &mut ForestChildren(ref mut forest) => { - match forest.get_mut(i) { - None => error_invalid_path(), - Some(tree) => tree - } - } - &mut TextChildren(_) => error_invalid_path() - } - } -} - -impl<'l> IndexMut for Tree<'l> { - /// Get a mutable reference to the `i`th child, or error. - fn index_mut(&mut self, index: usize) -> &mut Tree<'l> { - self.children.index_mut(index) - } -} - -impl<'a, 'l> IndexMut<&'a [usize]> for Tree<'l> { - /// Get a mutable reference to the path (represented as a slice), - /// or error. - fn index_mut(&mut self, path: &[usize]) -> &mut Tree<'l> { - match path.split_first() { - None => self, - Some((&i, path)) => self[i].index_mut(path) - } - } -} - -impl<'a, 'l> IndexMut<&'a Path> for Tree<'l> { - /// Get a mutable reference to the path, or error. - fn index_mut(&mut self, path: &Path) -> &mut Tree<'l> { - self.index_mut(path.as_slice()) - } -} - - -// Errors // - -fn error_invalid_path() -> ! { - panic!("tree - encountered invalid path!"); -} - -fn error_not_texty() -> ! { - panic!("tree - expected a texty tree!"); -} - -fn error_not_foresty() -> ! { - panic!("tree - expected a foresty tree!"); -} - -#[cfg(test)] -pub fn example_tree() -> Tree<'static> { - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "hi"), - Tree::new_forest(&TEST_FOREST, vec!( - Tree::new_text(&TEST_TEXT, "world"))))) -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_trees() { - let mut tree = example_tree(); - assert_eq!(tree.len(), 2); - assert_eq!(tree[0].len(), 3); - assert_eq!(tree[1].len(), 1); - assert_eq!(tree[&vec!()].len(), 2); - assert_eq!(tree[&vec!(0)].len(), 3); - assert_eq!(tree[&vec!(1)].len(), 1); - assert_eq!(tree[&vec!(1, 0)].len(), 6); - - { - let root = &tree; - let hi = &tree[0]; - - assert!(root.is_foresty()); - assert!(!root.is_texty()); - assert!(!hi.is_foresty()); - assert!(hi.is_texty()); - - assert_eq!(hi.text().as_str(), "hi"); - assert_eq!(root.forest().len(), 2); - } - { - let world = &mut tree[1][0]; - assert_eq!(world.text_mut().as_str(), "world"); - } - { - let sub = &mut tree[1]; - assert_eq!(sub.forest_mut().len(), 1); - } - } - - #[test] - #[should_panic(expected = "expected a texty tree")] - fn test_text_panic() { - example_tree().text(); - } - - #[test] - #[should_panic(expected = "expected a foresty tree")] - fn test_forest_panic() { - example_tree()[0].forest(); - } -} diff --git a/old-stuff/tree/tree_mut.rs b/old-stuff/tree/tree_mut.rs deleted file mode 100644 index 8681aa0..0000000 --- a/old-stuff/tree/tree_mut.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::mem; - -use tree::path::Path; -use tree::node::Node; -use tree::tree::Tree; -use tree::tree_ref::TreeRef; - - -/// A mutable reference to a subtree of a containing tree. -/// The containing tree is called the root. -/// Calling `.goto_child(i)` or `.goto_parent()` will change the -/// subtree, but leave the root the same. -/// -/// Notice that, unlike for TreeRefs, navigation operations consume -/// the TreeMut. -pub struct TreeMut<'t, 'l : 't> { - root: &'t mut Tree<'l>, - path: Path -} - -impl<'t, 'l> Tree<'l> { - pub(crate) fn as_mut(&'t mut self) -> TreeMut<'t, 'l> { - TreeMut{ - root: self, - path: vec!() - } - } -} - -impl<'t, 'l> TreeMut<'t, 'l> { - fn tree(&self) -> &Tree<'l> { - &self.root[&self.path] - } - - fn tree_parent(&self) -> &Tree<'l> { - &self.root[&self.path[0..self.path.len()-1]] - } - - fn tree_mut(&mut self) -> &mut Tree<'l> { - &mut self.root[&self.path] - } - - fn ancestor_mut(&mut self, gen: usize) -> &mut Tree<'l> { - // gen is the number of generations *back* to go. - &mut self.root[&self.path[0 .. self.path.len() - gen]] - } - - // All Trees // - - /// *This must be called every time this tree or one of its - /// children is modifed.* - pub fn update(&mut self) { - let n = self.path.len(); - for gen in 0 .. n + 1 { - self.ancestor_mut(gen).local_update(); - } - } - - pub fn as_ref<'t2>(&'t2 self) -> TreeRef<'t2, 'l> { - TreeRef::new(&self.root, self.path.clone()) - } - - pub fn replace(&mut self, tree: Tree<'l>) -> Tree<'l> { - mem::replace(&mut self.tree_mut(), tree) - } - - // Local Info // - - pub fn node(&self) -> &Node { - &self.tree().node - } - - pub fn breadcrumb(&self) -> usize { - self.tree().node.breadcrumb - } - - pub fn breadcrumb_mut(&mut self) -> &mut usize { - &mut self.tree_mut().node.breadcrumb - } - - pub fn len(&self) -> usize { - self.tree().len() - } - - pub fn path(&self) -> &Path { // for testing - &self.path - } - - // Texty Trees // - - pub fn is_texty(&self) -> bool { - self.tree().is_texty() - } - - pub fn text(&self) -> &String { - self.tree().text() - } - - /// DANGER: If the text is modified, its parent's node must be updated! - pub fn text_mut(&mut self) -> &mut String { - self.tree_mut().text_mut() - } - - // Foresty Trees // - - pub fn is_foresty(&self) -> bool { - self.tree().is_foresty() - } -/* - pub fn forest(&self) -> &Vec> { - self.tree().forest() - } -*/ - /// DANGER: If the forest is modified, its parent's node must be updated! - pub fn forest_mut(&mut self) -> &mut Vec> { - self.tree_mut().forest_mut() - } - - pub fn num_children(&self) -> usize { - self.tree().forest().len() - } - - pub fn goto_child(&mut self, i: usize) { - self.path.push(i); - } - - // Trees with Parents // - - pub fn has_parent(&self) -> bool { - !self.path.is_empty() - } - - pub fn index(&self) -> usize { - *self.path.last().expect("tree_mut: index") - } - - pub fn num_siblings(&self) -> usize { - self.tree_parent().forest().len() - } - - pub fn goto_parent(&mut self) -> usize { - self.path.pop().unwrap() - } -} - - -#[cfg(test)] -mod tests { - use tree::tree::example_tree; - - #[test] - fn test_tree_mut_goto() { - let mut tree = example_tree(); - let mut r = tree.as_mut(); - r.goto_child(1); - r.goto_parent(); - r.goto_child(1); - r.goto_child(0); - r.goto_parent(); - assert_eq!(r.as_ref().root().len(), 2); - assert_eq!(r.len(), 1); - } -} diff --git a/old-stuff/tree/tree_ref.rs b/old-stuff/tree/tree_ref.rs deleted file mode 100644 index 760f0a5..0000000 --- a/old-stuff/tree/tree_ref.rs +++ /dev/null @@ -1,126 +0,0 @@ -use geometry::*; -use syntax::{BoundSet, LayoutRegion}; -use tree::path::{Path, extend_path}; -use tree::node::Node; -use tree::tree::Tree; - -/// A reference to a subtree of a containing tree. -/// The containing tree is called the root. -/// Obtain one by calling `.as_ref()` on a Tree. -/// Calling `.child(i)` will change the subtree, but leave the root -/// the same. -#[derive(Clone)] -pub struct TreeRef<'t, 'l : 't> { - root: &'t Tree<'l>, - path: Path -} - -impl<'t, 'l> Tree<'l> { - /// Obtain a TreeRef, whose root and subtree are both this tree. - pub fn as_ref(&'t self) -> TreeRef<'t, 'l> { - TreeRef{ - root: &self, - path: vec!() - } - } -} - -impl<'t, 'l> TreeRef<'t, 'l> { - - pub(in tree) fn new(root: &'t Tree<'l>, path: Path) -> TreeRef<'t, 'l> { - TreeRef{ - root: root, - path: path - } - } - - fn tree(&self) -> &Tree<'l> { - &self.root[&self.path] - } - - /// Lay out the document to fit within the Bound. - pub fn lay_out(&self, region: Region) -> LayoutRegion { - let child_bounds = if self.is_foresty() { - let n = self.len(); - (0..n).map(|i| self.child(i).node().bounds.clone()).collect() - } else { - vec!(BoundSet::literal(self.text())) - }; - let empty_text = self.is_texty() && self.text().is_empty(); - let syn = &self.node().construct.syntax; - let arity = self.node().construct.arity.arity(); - let layouts = syn.lay_out(arity, &child_bounds, empty_text); - let region = Region{ - pos: region.pos, - bound: self.node().bounds.fit_bound(region.bound) - }; - layouts.pick(region.bound).regionize(region) - } - - /// The root (the containing tree). - pub fn root(&self) -> TreeRef<'t, 'l> { - TreeRef{ - root: self.root, - path: vec!() - } - } - - /// The path from the root to this tree. - pub fn path(&self) -> &Path { - &self.path - } - - // All Trees // - - /// This tree's node. - pub fn node(&self) -> &Node<'l> { - &self.tree().node - } - - /// The number of children of a foresty tree, - /// or the number of characters plus one of a texty tree. - pub fn len(&self) -> usize { - self.tree().len() - } - - // Texty Trees // - - /// Is this tree texty? - pub fn is_texty(&self) -> bool { - self.tree().is_texty() - } - - /// The text of a texty tree. Panics on foresty trees. - pub fn text(&self) -> &String { - self.tree().text() - } - - // Foresty Trees // - - /// Is this tree foresty? - pub fn is_foresty(&self) -> bool { - self.tree().is_foresty() - } - - /// Get the `i`th child. - pub fn child(&self, i: usize) -> TreeRef<'t, 'l> { - TreeRef{ - root: &self.root, - path: extend_path(&self.path, i) - } - } -} - - -#[cfg(test)] -mod tests { - use tree::tree::example_tree; - - #[test] - fn test_tree_ref_child() { - let tree = example_tree(); - let r = tree.as_ref().child(1).child(0); - assert_eq!(r.root().len(), 2); - assert_eq!(r.len(), 6); - } -}