From 38396d4281ce8ced885a3be5b68ba26f9ebcbef0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 19:37:53 -0800 Subject: [PATCH] Add remaining tests co-authored-by: Conrad --- crates/feedback/src/feedback_modal.rs | 41 +- crates/gpui/src/action.rs | 2 +- crates/gpui/src/keymap/matcher.rs | 635 ++++++++++---------- crates/gpui/src/platform/mac/text_system.rs | 200 ++---- 4 files changed, 362 insertions(+), 516 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index bf7a071560..2444a8e948 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -525,43 +525,4 @@ impl Render for FeedbackModal { } } -// TODO: Testing of various button states, dismissal prompts, etc. - -// #[cfg(test)] -// mod test { -// use super::*; - -// #[test] -// fn test_invalid_email_addresses() { -// let markdown = markdown.await.log_err(); -// let buffer = project.update(&mut cx, |project, cx| { -// project.create_buffer("", markdown, cx) -// })??; - -// workspace.update(&mut cx, |workspace, cx| { -// let system_specs = SystemSpecs::new(cx); - -// workspace.toggle_modal(cx, move |cx| { -// let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx); - -// assert!(!feedback_modal.can_submit()); -// assert!(!feedback_modal.valid_email_address(cx)); -// assert!(!feedback_modal.valid_character_count()); - -// feedback_modal -// .email_address_editor -// .update(cx, |this, cx| this.set_text("a", cx)); -// feedback_modal.set_submission_state(cx); - -// assert!(!feedback_modal.valid_email_address(cx)); - -// feedback_modal -// .email_address_editor -// .update(cx, |this, cx| this.set_text("a&b.com", cx)); -// feedback_modal.set_submission_state(cx); - -// assert!(feedback_modal.valid_email_address(cx)); -// }); -// })?; -// } -// } +// TODO: Testing of various button states, dismissal prompts, etc. :) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index ef02316f83..9caa0da482 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -170,7 +170,7 @@ impl ActionRegistry { macro_rules! actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { $( - #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::private::serde_derive::Deserialize)] + #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)] #[serde(crate = "gpui::private::serde")] pub struct $name; diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index ab42f1278c..fb508766a1 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -28,11 +28,11 @@ impl KeystrokeMatcher { /// Pushes a keystroke onto the matcher. /// The result of the new keystroke is returned: - /// KeyMatch::None => + /// - KeyMatch::None => /// No match is valid for this key given any pending keystrokes. - /// KeyMatch::Pending => + /// - KeyMatch::Pending => /// There exist bindings which are still waiting for more keys. - /// KeyMatch::Complete(matches) => + /// - KeyMatch::Complete(matches) => /// One or more bindings have received the necessary key presses. /// Bindings added later will take precedence over earlier bindings. pub fn match_keystroke( @@ -77,12 +77,10 @@ impl KeystrokeMatcher { if let Some(pending_key) = pending_key { self.pending_keystrokes.push(pending_key); - } - - if self.pending_keystrokes.is_empty() { - KeyMatch::None - } else { KeyMatch::Pending + } else { + self.pending_keystrokes.clear(); + KeyMatch::None } } } @@ -98,367 +96,374 @@ impl KeyMatch { pub fn is_some(&self) -> bool { matches!(self, KeyMatch::Some(_)) } + + pub fn matches(self) -> Option>> { + match self { + KeyMatch::Some(matches) => Some(matches), + _ => None, + } + } } -// #[cfg(test)] -// mod tests { -// use anyhow::Result; -// use serde::Deserialize; +impl PartialEq for KeyMatch { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (KeyMatch::None, KeyMatch::None) => true, + (KeyMatch::Pending, KeyMatch::Pending) => true, + (KeyMatch::Some(a), KeyMatch::Some(b)) => { + if a.len() != b.len() { + return false; + } -// use crate::{actions, impl_actions, keymap_matcher::ActionContext}; + for (a, b) in a.iter().zip(b.iter()) { + if !a.partial_eq(b.as_ref()) { + return false; + } + } -// use super::*; + true + } + _ => false, + } + } +} -// #[test] -// fn test_keymap_and_view_ordering() -> Result<()> { -// actions!(test, [EditorAction, ProjectPanelAction]); +#[cfg(test)] +mod tests { -// let mut editor = ActionContext::default(); -// editor.add_identifier("Editor"); + use serde_derive::Deserialize; -// let mut project_panel = ActionContext::default(); -// project_panel.add_identifier("ProjectPanel"); + use super::*; + use crate::{self as gpui, KeyBindingContextPredicate, Modifiers}; + use crate::{actions, KeyBinding}; -// // Editor 'deeper' in than project panel -// let dispatch_path = vec![(2, editor), (1, project_panel)]; + #[test] + fn test_keymap_and_view_ordering() { + actions!(test, [EditorAction, ProjectPanelAction]); -// // But editor actions 'higher' up in keymap -// let keymap = Keymap::new(vec![ -// Binding::new("left", EditorAction, Some("Editor")), -// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")), -// ]); + let mut editor = KeyContext::default(); + editor.add("Editor"); -// let mut matcher = KeymapMatcher::new(keymap); + let mut project_panel = KeyContext::default(); + project_panel.add("ProjectPanel"); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![ -// (2, Box::new(EditorAction)), -// (1, Box::new(ProjectPanelAction)), -// ]), -// ); + // Editor 'deeper' in than project panel + let dispatch_path = vec![project_panel, editor]; -// Ok(()) -// } + // But editor actions 'higher' up in keymap + let keymap = Keymap::new(vec![ + KeyBinding::new("left", EditorAction, Some("Editor")), + KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")), + ]); -// #[test] -// fn test_push_keystroke() -> Result<()> { -// actions!(test, [B, AB, C, D, DA, E, EF]); + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); -// let mut context1 = ActionContext::default(); -// context1.add_identifier("1"); + let matches = matcher + .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path) + .matches() + .unwrap(); -// let mut context2 = ActionContext::default(); -// context2.add_identifier("2"); + assert!(matches[0].partial_eq(&EditorAction)); + assert!(matches.get(1).is_none()); + } -// let dispatch_path = vec![(2, context2), (1, context1)]; + #[test] + fn test_multi_keystroke_match() { + actions!(test, [B, AB, C, D, DA, E, EF]); -// let keymap = Keymap::new(vec![ -// Binding::new("a b", AB, Some("1")), -// Binding::new("b", B, Some("2")), -// Binding::new("c", C, Some("2")), -// Binding::new("d", D, Some("1")), -// Binding::new("d", D, Some("2")), -// Binding::new("d a", DA, Some("2")), -// ]); + let mut context1 = KeyContext::default(); + context1.add("1"); -// let mut matcher = KeymapMatcher::new(keymap); + let mut context2 = KeyContext::default(); + context2.add("2"); -// // Binding with pending prefix always takes precedence -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Pending, -// ); -// // B alone doesn't match because a was pending, so AB is returned instead -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(1, Box::new(AB))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); + let dispatch_path = vec![context2, context1]; -// // Without an a prefix, B is dispatched like expected -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(B))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); + let keymap = Keymap::new(vec![ + KeyBinding::new("a b", AB, Some("1")), + KeyBinding::new("b", B, Some("2")), + KeyBinding::new("c", C, Some("2")), + KeyBinding::new("d", D, Some("1")), + KeyBinding::new("d", D, Some("2")), + KeyBinding::new("d a", DA, Some("2")), + ]); -// // If a is prefixed, C will not be dispatched because there -// // was a pending binding for it -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Pending, -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()), -// KeyMatch::None, -// ); -// assert!(!matcher.has_pending_keystrokes()); + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); -// // If a single keystroke matches multiple bindings in the tree -// // all of them are returned so that we can fallback if the action -// // handler decides to propagate the action -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]), -// ); + // Binding with pending prefix always takes precedence + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), + KeyMatch::Pending, + ); + // B alone doesn't match because a was pending, so AB is returned instead + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path), + KeyMatch::Some(vec![Box::new(AB)]), + ); + assert!(!matcher.has_pending_keystrokes()); -// // If none of the d action handlers consume the binding, a pending -// // binding may then be used -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(DA))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); + // Without an a prefix, B is dispatched like expected + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]), + KeyMatch::Some(vec![Box::new(B)]), + ); + assert!(!matcher.has_pending_keystrokes()); -// Ok(()) -// } + eprintln!("PROBLEM AREA"); + // If a is prefixed, C will not be dispatched because there + // was a pending binding for it + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), + KeyMatch::Pending, + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path), + KeyMatch::None, + ); + assert!(!matcher.has_pending_keystrokes()); -// #[test] -// fn test_keystroke_parsing() -> Result<()> { -// assert_eq!( -// Keystroke::parse("ctrl-p")?, -// Keystroke { -// key: "p".into(), -// ctrl: true, -// alt: false, -// shift: false, -// cmd: false, -// function: false, -// ime_key: None, -// } -// ); + // If a single keystroke matches multiple bindings in the tree + // only one of them is returned. + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path), + KeyMatch::Some(vec![Box::new(D)]), + ); + } -// assert_eq!( -// Keystroke::parse("alt-shift-down")?, -// Keystroke { -// key: "down".into(), -// ctrl: false, -// alt: true, -// shift: true, -// cmd: false, -// function: false, -// ime_key: None, -// } -// ); + #[test] + fn test_keystroke_parsing() { + assert_eq!( + Keystroke::parse("ctrl-p").unwrap(), + Keystroke { + key: "p".into(), + modifiers: Modifiers { + control: true, + alt: false, + shift: false, + command: false, + function: false, + }, + ime_key: None, + } + ); -// assert_eq!( -// Keystroke::parse("shift-cmd--")?, -// Keystroke { -// key: "-".into(), -// ctrl: false, -// alt: false, -// shift: true, -// cmd: true, -// function: false, -// ime_key: None, -// } -// ); + assert_eq!( + Keystroke::parse("alt-shift-down").unwrap(), + Keystroke { + key: "down".into(), + modifiers: Modifiers { + control: false, + alt: true, + shift: true, + command: false, + function: false, + }, + ime_key: None, + } + ); -// Ok(()) -// } + assert_eq!( + Keystroke::parse("shift-cmd--").unwrap(), + Keystroke { + key: "-".into(), + modifiers: Modifiers { + control: false, + alt: false, + shift: true, + command: true, + function: false, + }, + ime_key: None, + } + ); + } -// #[test] -// fn test_context_predicate_parsing() -> Result<()> { -// use KeymapContextPredicate::*; + #[test] + fn test_context_predicate_parsing() { + use KeyBindingContextPredicate::*; -// assert_eq!( -// KeymapContextPredicate::parse("a && (b == c || d != e)")?, -// And( -// Box::new(Identifier("a".into())), -// Box::new(Or( -// Box::new(Equal("b".into(), "c".into())), -// Box::new(NotEqual("d".into(), "e".into())), -// )) -// ) -// ); + assert_eq!( + KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(), + And( + Box::new(Identifier("a".into())), + Box::new(Or( + Box::new(Equal("b".into(), "c".into())), + Box::new(NotEqual("d".into(), "e".into())), + )) + ) + ); -// assert_eq!( -// KeymapContextPredicate::parse("!a")?, -// Not(Box::new(Identifier("a".into())),) -// ); + assert_eq!( + KeyBindingContextPredicate::parse("!a").unwrap(), + Not(Box::new(Identifier("a".into())),) + ); + } -// Ok(()) -// } + #[test] + fn test_context_predicate_eval() { + let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap(); -// #[test] -// fn test_context_predicate_eval() { -// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); + let mut context = KeyContext::default(); + context.add("a"); + assert!(!predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// assert!(!predicate.eval(&[context])); + let mut context = KeyContext::default(); + context.add("a"); + context.add("b"); + assert!(predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_identifier("b"); -// assert!(predicate.eval(&[context])); + let mut context = KeyContext::default(); + context.add("a"); + context.set("c", "x"); + assert!(!predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_key("c", "x"); -// assert!(!predicate.eval(&[context])); + let mut context = KeyContext::default(); + context.add("a"); + context.set("c", "d"); + assert!(predicate.eval(&[context])); -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_key("c", "d"); -// assert!(predicate.eval(&[context])); + let predicate = KeyBindingContextPredicate::parse("!a").unwrap(); + assert!(predicate.eval(&[KeyContext::default()])); + } -// let predicate = KeymapContextPredicate::parse("!a").unwrap(); -// assert!(predicate.eval(&[ActionContext::default()])); -// } + #[test] + fn test_context_child_predicate_eval() { + let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap(); + let contexts = [ + context_set(&["e", "f"]), + context_set(&["c", "d"]), // match this context + context_set(&["a", "b"]), + ]; -// #[test] -// fn test_context_child_predicate_eval() { -// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); -// let contexts = [ -// context_set(&["e", "f"]), -// context_set(&["c", "d"]), // match this context -// context_set(&["a", "b"]), -// ]; + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); -// assert!(!predicate.eval(&contexts[0..])); -// assert!(predicate.eval(&contexts[1..])); -// assert!(!predicate.eval(&contexts[2..])); + let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap(); + let contexts = [ + context_set(&["f"]), + context_set(&["e"]), // only match this context + context_set(&["c"]), + context_set(&["a", "b"]), + context_set(&["e"]), + context_set(&["c", "d"]), + context_set(&["a", "b"]), + ]; -// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); -// let contexts = [ -// context_set(&["f"]), -// context_set(&["e"]), // only match this context -// context_set(&["c"]), -// context_set(&["a", "b"]), -// context_set(&["e"]), -// context_set(&["c", "d"]), -// context_set(&["a", "b"]), -// ]; + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[3..])); + assert!(!predicate.eval(&contexts[4..])); + assert!(!predicate.eval(&contexts[5..])); + assert!(!predicate.eval(&contexts[6..])); -// assert!(!predicate.eval(&contexts[0..])); -// assert!(predicate.eval(&contexts[1..])); -// assert!(!predicate.eval(&contexts[2..])); -// assert!(!predicate.eval(&contexts[3..])); -// assert!(!predicate.eval(&contexts[4..])); -// assert!(!predicate.eval(&contexts[5..])); -// assert!(!predicate.eval(&contexts[6..])); + fn context_set(names: &[&str]) -> KeyContext { + let mut keymap = KeyContext::default(); + names.iter().for_each(|name| keymap.add(name.to_string())); + keymap + } + } -// fn context_set(names: &[&str]) -> ActionContext { -// let mut keymap = ActionContext::new(); -// names -// .iter() -// .for_each(|name| keymap.add_identifier(name.to_string())); -// keymap -// } -// } + #[test] + fn test_matcher() { + #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] + pub struct A(pub String); + impl_actions!(test, [A]); + actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); -// #[test] -// fn test_matcher() -> Result<()> { -// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] -// pub struct A(pub String); -// impl_actions!(test, [A]); -// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); + #[derive(Clone, Debug, Eq, PartialEq)] + struct ActionArg { + a: &'static str, + } -// #[derive(Clone, Debug, Eq, PartialEq)] -// struct ActionArg { -// a: &'static str, -// } + let keymap = Keymap::new(vec![ + KeyBinding::new("a", A("x".to_string()), Some("a")), + KeyBinding::new("b", B, Some("a")), + KeyBinding::new("a b", Ab, Some("a || b")), + KeyBinding::new("$", Dollar, Some("a")), + KeyBinding::new("\"", Quote, Some("a")), + KeyBinding::new("alt-s", Ess, Some("a")), + KeyBinding::new("ctrl-`", Backtick, Some("a")), + ]); -// let keymap = Keymap::new(vec![ -// Binding::new("a", A("x".to_string()), Some("a")), -// Binding::new("b", B, Some("a")), -// Binding::new("a b", Ab, Some("a || b")), -// Binding::new("$", Dollar, Some("a")), -// Binding::new("\"", Quote, Some("a")), -// Binding::new("alt-s", Ess, Some("a")), -// Binding::new("ctrl-`", Backtick, Some("a")), -// ]); + let mut context_a = KeyContext::default(); + context_a.add("a"); -// let mut context_a = ActionContext::default(); -// context_a.add_identifier("a"); + let mut context_b = KeyContext::default(); + context_b.add("b"); -// let mut context_b = ActionContext::default(); -// context_b.add_identifier("b"); + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); -// let mut matcher = KeymapMatcher::new(keymap); + // Basic match + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(A("x".to_string()))]) + ); + matcher.clear_pending(); -// // Basic match -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) -// ); -// matcher.clear_pending(); + // Multi-keystroke match + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]), + KeyMatch::Pending + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), + KeyMatch::Some(vec![Box::new(Ab)]) + ); + matcher.clear_pending(); -// // Multi-keystroke match -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ab))]) -// ); -// matcher.clear_pending(); + // Failed matches don't interfere with matching subsequent keys + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]), + KeyMatch::None + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(A("x".to_string()))]) + ); + matcher.clear_pending(); -// // Failed matches don't interfere with matching subsequent keys -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]), -// KeyMatch::None -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) -// ); -// matcher.clear_pending(); + let mut context_c = KeyContext::default(); + context_c.add("c"); -// // Pending keystrokes are cleared when the context changes -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]), -// KeyMatch::None -// ); -// matcher.clear_pending(); + assert_eq!( + matcher.match_keystroke( + &Keystroke::parse("a").unwrap(), + &[context_c.clone(), context_b.clone()] + ), + KeyMatch::Pending + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), + KeyMatch::Some(vec![Box::new(Ab)]) + ); -// let mut context_c = ActionContext::default(); -// context_c.add_identifier("c"); + // handle Czech $ (option + 4 key) + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Dollar)]) + ); -// // Pending keystrokes are maintained per-view -// assert_eq!( -// matcher.match_keystroke( -// Keystroke::parse("a")?, -// vec![(1, context_b.clone()), (2, context_c.clone())] -// ), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ab))]) -// ); + // handle Brazillian quote (quote key then space key) + assert_eq!( + matcher.match_keystroke( + &Keystroke::parse("space->\"").unwrap(), + &[context_a.clone()] + ), + KeyMatch::Some(vec![Box::new(Quote)]) + ); -// // handle Czech $ (option + 4 key) -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Dollar))]) -// ); + // handle ctrl+` on a brazillian keyboard + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Backtick)]) + ); -// // handle Brazillian quote (quote key then space key) -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Quote))]) -// ); - -// // handle ctrl+` on a brazillian keyboard -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Backtick))]) -// ); - -// // handle alt-s on a US keyboard -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ess))]) -// ); - -// Ok(()) -// } -// } + // handle alt-s on a US keyboard + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("alt-s->ß").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Ess)]) + ); + } +} diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index d9f7936066..79ffb8dc8e 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -592,169 +592,49 @@ impl From for FontkitStyle { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::AppContext; -// use font_kit::properties::{Style, Weight}; -// use platform::FontSystem as _; +#[cfg(test)] +mod tests { + use crate::{font, px, FontRun, MacTextSystem, PlatformTextSystem}; -// #[crate::test(self, retries = 5)] -// fn test_layout_str(_: &mut AppContext) { -// // This is failing intermittently on CI and we don't have time to figure it out -// let fonts = FontSystem::new(); -// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap(); -// let menlo_regular = RunStyle { -// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo_italic = RunStyle { -// font_id: fonts -// .select_font(&menlo, Properties::new().style(Style::Italic)) -// .unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo_bold = RunStyle { -// font_id: fonts -// .select_font(&menlo, Properties::new().weight(Weight::BOLD)) -// .unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// assert_ne!(menlo_regular, menlo_italic); -// assert_ne!(menlo_regular, menlo_bold); -// assert_ne!(menlo_italic, menlo_bold); + #[test] + fn test_wrap_line() { + let fonts = MacTextSystem::new(); + let font_id = fonts.font_id(&font("Helvetica")).unwrap(); -// let line = fonts.layout_line( -// "hello world", -// 16.0, -// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)], -// ); -// assert_eq!(line.runs.len(), 3); -// assert_eq!(line.runs[0].font_id, menlo_bold.font_id); -// assert_eq!(line.runs[0].glyphs.len(), 2); -// assert_eq!(line.runs[1].font_id, menlo_italic.font_id); -// assert_eq!(line.runs[1].glyphs.len(), 4); -// assert_eq!(line.runs[2].font_id, menlo_regular.font_id); -// assert_eq!(line.runs[2].glyphs.len(), 5); -// } + let line = "one two three four five\n"; + let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); + assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); -// #[test] -// fn test_glyph_offsets() -> crate::Result<()> { -// let fonts = FontSystem::new(); -// let zapfino = fonts.load_family("Zapfino", &Default::default())?; -// let zapfino_regular = RunStyle { -// font_id: fonts.select_font(&zapfino, &Properties::new())?, -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo = fonts.load_family("Menlo", &Default::default())?; -// let menlo_regular = RunStyle { -// font_id: fonts.select_font(&menlo, &Properties::new())?, -// color: Default::default(), -// underline: Default::default(), -// }; + let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; + let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); + assert_eq!( + wrap_boundaries, + &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] + ); + } -// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈"; -// let line = fonts.layout_line( -// text, -// 16.0, -// &[ -// (9, zapfino_regular), -// (13, menlo_regular), -// (text.len() - 22, zapfino_regular), -// ], -// ); -// assert_eq!( -// line.runs -// .iter() -// .flat_map(|r| r.glyphs.iter()) -// .map(|g| g.index) -// .collect::>(), -// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37], -// ); -// Ok(()) -// } + #[test] + fn test_layout_line_bom_char() { + let fonts = MacTextSystem::new(); + let font_id = fonts.font_id(&font("Helvetica")).unwrap(); + let line = "\u{feff}"; + let mut style = FontRun { + font_id, + len: line.len(), + }; -// #[test] -// #[ignore] -// fn test_rasterize_glyph() { -// use std::{fs::File, io::BufWriter, path::Path}; + let layout = fonts.layout_line(line, px(16.), &[style]); + assert_eq!(layout.len, line.len()); + assert!(layout.runs.is_empty()); -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap(); -// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); -// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap(); - -// const VARIANTS: usize = 1; -// for i in 0..VARIANTS { -// let variant = i as f32 / VARIANTS as f32; -// let (bounds, bytes) = fonts -// .rasterize_glyph( -// font_id, -// 16.0, -// glyph_id, -// vec2f(variant, variant), -// 2., -// RasterizationOptions::Alpha, -// ) -// .unwrap(); - -// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); -// let path = Path::new(&name); -// let file = File::create(path).unwrap(); -// let w = &mut BufWriter::new(file); - -// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32); -// encoder.set_color(png::ColorType::Grayscale); -// encoder.set_depth(png::BitDepth::Eight); -// let mut writer = encoder.write_header().unwrap(); -// writer.write_image_data(&bytes).unwrap(); -// } -// } - -// #[test] -// fn test_wrap_line() { -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); -// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); - -// let line = "one two three four five\n"; -// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); -// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); - -// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; -// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); -// assert_eq!( -// wrap_boundaries, -// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] -// ); -// } - -// #[test] -// fn test_layout_line_bom_char() { -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); -// let style = RunStyle { -// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; - -// let line = "\u{feff}"; -// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); -// assert_eq!(layout.len, line.len()); -// assert!(layout.runs.is_empty()); - -// let line = "a\u{feff}b"; -// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); -// assert_eq!(layout.len, line.len()); -// assert_eq!(layout.runs.len(), 1); -// assert_eq!(layout.runs[0].glyphs.len(), 2); -// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a -// // There's no glyph for \u{feff} -// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b -// } -// } + let line = "a\u{feff}b"; + style.len = line.len(); + let layout = fonts.layout_line(line, px(16.), &[style]); + assert_eq!(layout.len, line.len()); + assert_eq!(layout.runs.len(), 1); + assert_eq!(layout.runs[0].glyphs.len(), 2); + assert_eq!(layout.runs[0].glyphs[0].id, 68u32.into()); // a + // There's no glyph for \u{feff} + assert_eq!(layout.runs[0].glyphs[1].id, 69u32.into()); // b + } +}