mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-09 20:12:57 +00:00
Merge branch 'craft-keymap' into keymap-ui
# Conflicts: # crates/keymap/README.md # crates/keymap/src/lib.rs
This commit is contained in:
commit
cf9e10a90a
10 files changed
+163
-163
No files matched your search
@@ -39,7 +39,7 @@ craft-<name> = { git = "https://github.com/storytold/craft-libs", rev = "<commit
|
||||
language setting.
|
||||
- [`craft-keymap`](crates/keymap/README.md): keyboard bindings: chords and their text forms,
|
||||
scopes, conflicts, user overrides stored through craft-settings and import of other
|
||||
applications' shortcut sets. Command catalogues, file readers and the shortcut editor UI stay
|
||||
applications' shortcut sets. Command catalogs, file readers and the shortcut editor UI stay
|
||||
in each app.
|
||||
- [`craft-print`](crates/print/README.md): PDF submission to CUPS and Windows, printer discovery,
|
||||
duplex/copies/colour and driver preferences. PDF generation stays in each app.
|
||||
|
||||
+14
-14
@@ -3,12 +3,12 @@
|
||||
`craft-keymap` holds the data and logic for keyboard bindings in the Craft apps: chords and their
|
||||
text forms, bindings from chords to commands, scopes, conflicts, user overrides, named sets, an
|
||||
import pipeline for other applications' shortcut sets, and an editor model. Each application
|
||||
supplies its command catalogue, its file-format readers and its UI.
|
||||
supplies its command catalog, its file-format readers and its UI.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Data and logic, plus a shared Keyboard Shortcuts dialog behind the `ui` feature. The apps keep
|
||||
the command catalogue (ids, labels, menu paths, defaults), the readers for other applications'
|
||||
the command catalog (ids, labels, menu paths, defaults), the readers for other applications'
|
||||
files, file dialogs and running the commands.
|
||||
- Never crashes: no panics, no `unsafe`. Loading settings never fails; what cannot be used is
|
||||
kept or reported (`Keymap::problems`, the import report).
|
||||
@@ -20,7 +20,7 @@ supplies its command catalogue, its file-format readers and its UI.
|
||||
- Dependencies: `serde` and `serde_json`; `egui` with the `egui` feature; `craft-ui` and
|
||||
`craft-i18n` with the `ui` feature.
|
||||
|
||||
## A catalogue and dispatch
|
||||
## A catalog and dispatch
|
||||
|
||||
A command with parameters has the key `window.panel {"panel":"layers"}` in settings and reports.
|
||||
Tools of one group share a key and cycle. A binding can name a scope other than the command's
|
||||
@@ -33,7 +33,7 @@ use serde_json::json;
|
||||
let key = |text: &str| Binding::parse(text).unwrap();
|
||||
let layers = CommandRef::with_params("window.panel", json!({"panel": "layers"}));
|
||||
let select = Kind::Tool { group: Some("select".into()) };
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
Bindable::new("file.save", "Save", Scope::app()).with_path(vec!["File".into(), "Save".into()]).with_default(key("Cmd+S")),
|
||||
Bindable::new(layers.clone(), "Layers", Scope::app()).with_path(vec!["Window".into(), "Layers".into()]).with_default(key("F7")),
|
||||
Bindable::new("tool.marquee", "Marquee Tool", Scope::app()).with_kind(select.clone()).with_default(key("M")),
|
||||
@@ -44,7 +44,7 @@ let catalogue = vec![
|
||||
// An app command whose binding works only in the Text context.
|
||||
Bindable::new("type.bold", "Bold", Scope::app()).with_default(key("Text: Cmd+B")),
|
||||
];
|
||||
let keymap = Keymap::new(catalogue, Scopes::new().with_panel("Timeline").with_context("Text"), Platform::Mac);
|
||||
let keymap = Keymap::new(catalog, Scopes::new().with_panel("Timeline").with_context("Text"), Platform::Mac);
|
||||
|
||||
// Dispatch: the chord pressed, whether it is a double press, and what has the keyboard.
|
||||
let press = |text: &str, active: &Active| keymap.lookup(&Chord::parse(text).unwrap(), false, active);
|
||||
@@ -112,9 +112,9 @@ impl Settings for Prefs {
|
||||
const SCHEMA_VERSION: u32 = 1;
|
||||
}
|
||||
|
||||
// `catalogue` is the app's Vec<Bindable>.
|
||||
// `catalog` is the app's Vec<Bindable>.
|
||||
let (mut store, _report) = SettingsStore::<Prefs>::open(Location::Auto);
|
||||
let mut keymap = Keymap::new(catalogue, Scopes::new(), Platform::current()).with_overrides(store.get().shortcuts.clone());
|
||||
let mut keymap = Keymap::new(catalog, Scopes::new(), Platform::current()).with_overrides(store.get().shortcuts.clone());
|
||||
// Entries for unknown commands and texts that do not parse are kept and listed here.
|
||||
let _problems = keymap.problems();
|
||||
|
||||
@@ -216,9 +216,9 @@ assert_eq!((report.unmatched[0].item.as_str(), report.unmatched[0].line), ("File
|
||||
- Panel menus: sources list panel-menu items with the panel's name in front (InDesign's Panel
|
||||
Menus area writes `Character: All Caps`; Illustrator and Photoshop sets have similar areas).
|
||||
The reader splits the name into segments and marks the entry `ForeignEntry::in_panel_menu()`.
|
||||
Such an entry also matches a catalogue path that ends with its segments (with the command's
|
||||
Such an entry also matches a catalog path that ends with its segments (with the command's
|
||||
label or the path's last segment last), and its last segment alone as a label. The last
|
||||
segment alone counts only when the first segment, the panel, is a segment of some catalogue
|
||||
segment alone counts only when the first segment, the panel, is a segment of some catalog
|
||||
path, so `History: New Document` matches nothing in an app without a History panel. An entry
|
||||
without keys that matched only these ways leaves the command's shortcuts alone and is listed
|
||||
in `matched_without_change` (reason `PanelMenu`).
|
||||
@@ -255,7 +255,7 @@ assert_eq!((report.unmatched[0].item.as_str(), report.unmatched[0].line), ("File
|
||||
nothing; the user's answer is a second call with `Policy::Steal` or `Policy::Keep`.
|
||||
- `Recorder::press(chord, time)` gives `Single`, `Double` (the same chord again within
|
||||
`DOUBLE_PRESS`, 0.6 s) or `Cancel` (Escape).
|
||||
- `editor::search(&keymap, query, platform)` filters the catalogue by label, alias, menu path,
|
||||
- `editor::search(&keymap, query, platform)` filters the catalog by label, alias, menu path,
|
||||
command id and key text, canonical or as shown on `platform`.
|
||||
|
||||
```rust
|
||||
@@ -282,13 +282,13 @@ session.cancel(&mut keymap);
|
||||
## Shortcuts dialog
|
||||
|
||||
With the `ui` feature, `ui::ShortcutsDialog` draws the Keyboard Shortcuts dialog over a `Keymap`
|
||||
and, optionally, `Profiles`: a table of the catalogue grouped by menu, recording, conflicts, undo,
|
||||
and, optionally, `Profiles`: a table of the catalog grouped by menu, recording, conflicts, undo,
|
||||
named sets, import review and a summary. It edits the keymap in place, so menus show new keys at
|
||||
once, and Cancel puts everything back as it was when the dialog opened.
|
||||
|
||||
Adopt it in three steps:
|
||||
|
||||
1. Build the `Keymap` from the catalogue and keep the built-in sets (`Builtins`), as above.
|
||||
1. Build the `Keymap` from the catalog and keep the built-in sets (`Builtins`), as above.
|
||||
2. While the dialog is open, keep a `DialogState` and call `show` (a window) or `ui` (the body,
|
||||
for the app's own modal or preferences page) every frame.
|
||||
3. Act on the `Outcome` it returns, and drop the state when `is_open()` turns false.
|
||||
@@ -345,9 +345,9 @@ Key dispatch while the dialog is open:
|
||||
Words come from `ui::Text`. Apps on craft-i18n pass `I18nText`; others implement `ui` and
|
||||
`plural` over their own lookup, and `rtl` and `visual` (the app's bidi reordering) for
|
||||
right-to-left languages, which mirror the layout. Chord text is never reordered. `ui::STRINGS`
|
||||
and `ui::PLURALS` list every string the dialog looks up, for the translation catalogue.
|
||||
and `ui::PLURALS` list every string the dialog looks up, for the translation catalog.
|
||||
`Options` sets the conflict mode, double-press recording, area tabs, the on-screen keyboard,
|
||||
read-only rows for keys outside the catalogue, the app's import and export formats, and an
|
||||
read-only rows for keys outside the catalog, the app's import and export formats, and an
|
||||
`accept` rule for which keys a command may get.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -163,7 +163,7 @@ pub use crate::editor::DOUBLE_PRESS;
|
||||
pub struct Hit {
|
||||
/// The command to run; for tools sharing the chord, the first of `cycle`.
|
||||
pub command: CommandRef,
|
||||
/// Tools of one group sharing the chord, in catalogue order (the app picks the next one);
|
||||
/// Tools of one group sharing the chord, in catalog order (the app picks the next one);
|
||||
/// empty for a plain command.
|
||||
pub cycle: Vec<CommandRef>,
|
||||
/// The chord whose binding matched, with `double` set when the double binding ran.
|
||||
|
||||
+18
-18
@@ -53,8 +53,8 @@ pub struct ForeignEntry {
|
||||
/// submenu of [`ForeignEntry::menu`], which is usually `None`. Sources write such items as
|
||||
/// `Character: All Caps` (InDesign's Panel Menus area, the panel-menu areas of Illustrator and
|
||||
/// Photoshop sets); the reader splits them into segments. Such an entry also matches a
|
||||
/// catalogue path that ends with its path, and its last segment alone when its first segment
|
||||
/// is a segment of some catalogue path. Without keys, an entry matched only these ways
|
||||
/// catalog path that ends with its path, and its last segment alone when its first segment
|
||||
/// is a segment of some catalog path. Without keys, an entry matched only these ways
|
||||
/// changes nothing ([`ImportReport::matched_without_change`]).
|
||||
pub panel_menu: bool,
|
||||
}
|
||||
@@ -94,7 +94,7 @@ impl ForeignEntry {
|
||||
self.menu.iter().chain(&self.path).map(String::as_str).collect::<Vec<_>>().join(" > ")
|
||||
}
|
||||
|
||||
/// The key of [`ImportOptions::remembered`] for this entry: the normalised menu and path
|
||||
/// The key of [`ImportOptions::remembered`] for this entry: the normalized menu and path
|
||||
/// joined by U+001F.
|
||||
pub fn remember_key(&self) -> String {
|
||||
self.menu.iter().chain(&self.path).map(|s| normalize_label(s)).collect::<Vec<_>>().join("\u{1f}")
|
||||
@@ -344,7 +344,7 @@ pub struct MatchedWithoutChange {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum NoChangeReason {
|
||||
/// The entry matched only through a panel-menu rule (a catalogue path ending with its
|
||||
/// The entry matched only through a panel-menu rule (a catalog path ending with its
|
||||
/// segments, or its last segment alone). Such a match is close enough to bind the entry's
|
||||
/// keys, not to take a command's shortcuts away.
|
||||
PanelMenu,
|
||||
@@ -407,7 +407,7 @@ pub fn normalize_label(s: &str) -> String {
|
||||
s.split_whitespace().collect::<Vec<_>>().join(" ").to_lowercase()
|
||||
}
|
||||
|
||||
/// A normalised name without a leading `show/hide `, `show ` or `hide ` when something remains:
|
||||
/// A normalized name without a leading `show/hide `, `show ` or `hide ` when something remains:
|
||||
/// sources name a toggle by what choosing it does now. Only toggle commands
|
||||
/// ([`Bindable::is_toggle`]) are matched by this form.
|
||||
fn toggle_name(s: &str) -> String {
|
||||
@@ -466,7 +466,7 @@ pub fn plan(set: &ForeignSet, keymap: &Keymap, options: &ImportOptions) -> Impor
|
||||
|
||||
if by_panel && entry.keys.is_empty() {
|
||||
let (command, label) =
|
||||
keymap.catalogue().get(i).map(|b| (b.command.clone(), b.label.clone())).unwrap_or_else(|| (CommandRef::new(""), String::new()));
|
||||
keymap.catalog().get(i).map(|b| (b.command.clone(), b.label.clone())).unwrap_or_else(|| (CommandRef::new(""), String::new()));
|
||||
report.matched_without_change.push(MatchedWithoutChange { command, label, item, line: entry.line, reason: NoChangeReason::PanelMenu });
|
||||
continue;
|
||||
}
|
||||
@@ -511,7 +511,7 @@ pub fn plan(set: &ForeignSet, keymap: &Keymap, options: &ImportOptions) -> Impor
|
||||
}
|
||||
|
||||
let at = *planned_at.entry(i).or_insert_with(|| {
|
||||
let command = keymap.catalogue().get(i).map(|b| b.command.clone()).unwrap_or_else(|| CommandRef::new(""));
|
||||
let command = keymap.catalog().get(i).map(|b| b.command.clone()).unwrap_or_else(|| CommandRef::new(""));
|
||||
commands.push(Planned { command, item: item.clone(), line, bindings: Vec::new(), none_defined: true });
|
||||
commands.len() - 1
|
||||
});
|
||||
@@ -549,8 +549,8 @@ impl ImportPlan {
|
||||
/// both commands keep the bindings they had. With [`Start::Defaults`] that cannot happen.
|
||||
pub fn apply(&self, keymap: &mut Keymap) -> ImportReport {
|
||||
let before: Vec<Vec<Binding>> = self.commands.iter().map(|p| keymap.bindings(&p.command)).collect();
|
||||
// Dropping every entry leaves room for all of them: each catalogue command takes at most one
|
||||
// entry, and the catalogue holds at most `MAX_COMMANDS`.
|
||||
// Dropping every entry leaves room for all of them: each catalog command takes at most one
|
||||
// entry, and the catalog holds at most `MAX_COMMANDS`.
|
||||
if self.start == Start::Defaults {
|
||||
keymap.reset_all();
|
||||
}
|
||||
@@ -814,10 +814,10 @@ enum Found {
|
||||
None,
|
||||
}
|
||||
|
||||
/// Catalogue names in one form, for the match steps.
|
||||
/// Catalog names in one form, for the match steps.
|
||||
#[derive(Default)]
|
||||
struct Names {
|
||||
/// Normalised paths joined by U+001F.
|
||||
/// Normalized paths joined by U+001F.
|
||||
paths: HashMap<String, Vec<usize>>,
|
||||
/// The last two or more segments of paths, also with the label as the last one, joined by
|
||||
/// U+001F: for panel-menu entries.
|
||||
@@ -870,9 +870,9 @@ impl Names {
|
||||
}
|
||||
}
|
||||
|
||||
/// The lists the match steps look up for an entry's normalised menu and path, most specific
|
||||
/// The lists the match steps look up for an entry's normalized menu and path, most specific
|
||||
/// first, each with whether it is a panel-menu step. `panel` is `Some(known)` for a panel-menu
|
||||
/// entry, `known` saying whether the catalogue has its panel; only then does its last segment
|
||||
/// entry, `known` saying whether the catalog has its panel; only then does its last segment
|
||||
/// alone match.
|
||||
fn steps(&self, menu: Option<&String>, segs: &[String], panel: Option<bool>) -> Vec<(Option<&Vec<usize>>, bool)> {
|
||||
let Some(last) = segs.last() else { return Vec::new() };
|
||||
@@ -900,7 +900,7 @@ impl Names {
|
||||
}
|
||||
}
|
||||
|
||||
/// The catalogue by normalised path and label, for the match steps.
|
||||
/// The catalog by normalized path and label, for the match steps.
|
||||
struct Index {
|
||||
tool: Vec<bool>,
|
||||
/// Binding keys, for remembered answers.
|
||||
@@ -908,14 +908,14 @@ struct Index {
|
||||
names: Names,
|
||||
/// Toggle commands by their names without a leading Show/Hide ([`toggle_name`]).
|
||||
toggles: Names,
|
||||
/// Every normalised segment of every catalogue path: the panels and menus the app has.
|
||||
/// Every normalized segment of every catalog path: the panels and menus the app has.
|
||||
segments: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
fn new(keymap: &Keymap) -> Index {
|
||||
let mut ix = Index { tool: Vec::new(), keys: HashMap::new(), names: Names::default(), toggles: Names::default(), segments: HashSet::new() };
|
||||
for (i, b) in keymap.catalogue().iter().enumerate() {
|
||||
for (i, b) in keymap.catalog().iter().enumerate() {
|
||||
ix.tool.push(matches!(b.kind, Kind::Tool { .. }));
|
||||
ix.keys.insert(b.command.key(), i);
|
||||
ix.names.add(i, b, str::to_string);
|
||||
@@ -939,7 +939,7 @@ impl Index {
|
||||
/// The match steps in order; the first that finds exactly one command decides. The steps run
|
||||
/// on the names as written, then on toggle commands with the entry's label and theirs
|
||||
/// without a leading Show/Hide. A panel-menu entry's last segment alone matches only when
|
||||
/// its first segment, the panel, is a segment of some catalogue path.
|
||||
/// its first segment, the panel, is a segment of some catalog path.
|
||||
fn find(&self, entry: &ForeignEntry, tool: bool, remembered: &BTreeMap<String, CommandRef>) -> Found {
|
||||
if let Some(c) = remembered.get(&entry.remember_key())
|
||||
&& let Some(&i) = self.keys.get(&c.key())
|
||||
@@ -978,7 +978,7 @@ struct ToSuggest {
|
||||
|
||||
/// Fuzzy candidates for unmatched entries, among commands of the same kind.
|
||||
fn suggest(keymap: &Keymap, entries: &[ToSuggest]) -> Vec<Suggestion> {
|
||||
let pool = |tool: bool| -> Vec<&Bindable> { keymap.catalogue().iter().filter(|b| matches!(b.kind, Kind::Tool { .. }) == tool).collect() };
|
||||
let pool = |tool: bool| -> Vec<&Bindable> { keymap.catalog().iter().filter(|b| matches!(b.kind, Kind::Tool { .. }) == tool).collect() };
|
||||
let matcher = |pool: &[&Bindable]| {
|
||||
let candidates: Vec<namematch::Candidate> = pool
|
||||
.iter()
|
||||
|
||||
+48
-48
@@ -1,4 +1,4 @@
|
||||
//! A command catalogue with default bindings, the user's overrides, dispatch and conflicts.
|
||||
//! A command catalog with default bindings, the user's overrides, dispatch and conflicts.
|
||||
|
||||
pub(crate) mod overrides;
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::scope::{Active, Scope, ScopeKind, Scopes};
|
||||
pub use overrides::Overrides;
|
||||
pub use overrides::list as overrides_list;
|
||||
|
||||
/// Most commands a catalogue or an [`Overrides`] holds.
|
||||
/// Most commands a catalog or an [`Overrides`] holds.
|
||||
pub const MAX_COMMANDS: usize = 4096;
|
||||
/// Most bindings per command.
|
||||
pub const MAX_BINDINGS: usize = 16;
|
||||
@@ -226,7 +226,7 @@ impl TypingRule {
|
||||
pub enum Lookup {
|
||||
None,
|
||||
Run(CommandRef),
|
||||
/// Tools of one group share the chord: the app picks the next one (catalogue order).
|
||||
/// Tools of one group share the chord: the app picks the next one (catalog order).
|
||||
Cycle(Vec<CommandRef>),
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ pub enum AssignError {
|
||||
TooMany,
|
||||
/// Other commands hold the chord ([`Policy::Refuse`]).
|
||||
Taken(Vec<Holder>),
|
||||
/// A scope that is neither registered in [`Scopes`] nor used by the catalogue.
|
||||
/// A scope that is neither registered in [`Scopes`] nor used by the catalog.
|
||||
UnknownScope(Scope),
|
||||
/// The change needs a new [`Overrides`] entry and it already holds [`MAX_COMMANDS`].
|
||||
Full,
|
||||
@@ -311,13 +311,13 @@ pub enum DropReason {
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Problem {
|
||||
/// No command in the catalogue has this key; the entry is kept.
|
||||
/// No command in the catalog has this key; the entry is kept.
|
||||
UnknownCommand { key: String },
|
||||
/// A text that does not parse; the entry keeps it.
|
||||
BadBinding { key: String, text: String, error: BindingError },
|
||||
/// A reserved chord; not bound.
|
||||
Reserved { key: String, text: String },
|
||||
/// A scope that is neither registered in [`Scopes`] nor used by the catalogue; not bound.
|
||||
/// A scope that is neither registered in [`Scopes`] nor used by the catalog; not bound.
|
||||
UnknownScope { key: String, text: String },
|
||||
/// Another entry names the same command; this one is ignored.
|
||||
DuplicateKey { key: String },
|
||||
@@ -366,10 +366,10 @@ struct Group<'a> {
|
||||
/// one's command and binding.
|
||||
type Pair<'a> = (usize, usize, bool, (usize, &'a Bound), (usize, &'a Bound));
|
||||
|
||||
/// The catalogue, the scope registry, the platform and the user's overrides.
|
||||
/// The catalog, the scope registry, the platform and the user's overrides.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Keymap {
|
||||
catalogue: Vec<Bindable>,
|
||||
catalog: Vec<Bindable>,
|
||||
keys: Vec<String>,
|
||||
index: HashMap<String, usize>,
|
||||
defaults: Vec<Vec<Bound>>,
|
||||
@@ -381,8 +381,8 @@ pub struct Keymap {
|
||||
/// Whether the overrides hold a hand-written variant of a command's key
|
||||
/// (`window.panel { "panel": "layers" }`); only loading adds them.
|
||||
variants: bool,
|
||||
/// Home and default scopes of the catalogue.
|
||||
catalogue_scopes: HashSet<Scope>,
|
||||
/// Home and default scopes of the catalog.
|
||||
catalog_scopes: HashSet<Scope>,
|
||||
scopes: Scopes,
|
||||
platform: Platform,
|
||||
typing: TypingRule,
|
||||
@@ -393,9 +393,9 @@ pub struct Keymap {
|
||||
impl Keymap {
|
||||
/// A keymap with default bindings. Entries past [`MAX_COMMANDS`], with a key over
|
||||
/// [`MAX_KEY`] bytes, or repeating an earlier key are left out.
|
||||
pub fn new(catalogue: Vec<Bindable>, scopes: Scopes, platform: Platform) -> Keymap {
|
||||
pub fn new(catalog: Vec<Bindable>, scopes: Scopes, platform: Platform) -> Keymap {
|
||||
let mut km = Keymap {
|
||||
catalogue: Vec::new(),
|
||||
catalog: Vec::new(),
|
||||
keys: Vec::new(),
|
||||
index: HashMap::new(),
|
||||
defaults: Vec::new(),
|
||||
@@ -403,27 +403,27 @@ impl Keymap {
|
||||
by_trigger: HashMap::new(),
|
||||
stored: Vec::new(),
|
||||
variants: false,
|
||||
catalogue_scopes: HashSet::new(),
|
||||
catalog_scopes: HashSet::new(),
|
||||
scopes,
|
||||
platform,
|
||||
typing: TypingRule::default(),
|
||||
reserved: Vec::new(),
|
||||
overrides: Overrides::new(),
|
||||
};
|
||||
for b in catalogue {
|
||||
for b in catalog {
|
||||
let key = b.command.key();
|
||||
if km.catalogue.len() >= MAX_COMMANDS || key.len() > MAX_KEY || km.index.contains_key(&key) {
|
||||
if km.catalog.len() >= MAX_COMMANDS || key.len() > MAX_KEY || km.index.contains_key(&key) {
|
||||
continue;
|
||||
}
|
||||
let defaults = km.normalize(b.defaults.iter().cloned(), &b.scope);
|
||||
km.catalogue_scopes.insert(b.scope.clone());
|
||||
km.catalogue_scopes.extend(defaults.iter().map(|(_, s)| s.clone()));
|
||||
km.index.insert(key.clone(), km.catalogue.len());
|
||||
km.catalog_scopes.insert(b.scope.clone());
|
||||
km.catalog_scopes.extend(defaults.iter().map(|(_, s)| s.clone()));
|
||||
km.index.insert(key.clone(), km.catalog.len());
|
||||
km.keys.push(key);
|
||||
km.current.push(defaults.clone());
|
||||
km.defaults.push(defaults);
|
||||
km.stored.push(None);
|
||||
km.catalogue.push(b);
|
||||
km.catalog.push(b);
|
||||
}
|
||||
km.rebuild_index();
|
||||
km
|
||||
@@ -468,12 +468,12 @@ impl Keymap {
|
||||
self.typing
|
||||
}
|
||||
|
||||
pub fn catalogue(&self) -> &[Bindable] {
|
||||
&self.catalogue
|
||||
pub fn catalog(&self) -> &[Bindable] {
|
||||
&self.catalog
|
||||
}
|
||||
|
||||
pub fn bindable(&self, command: &CommandRef) -> Option<&Bindable> {
|
||||
self.find(command).map(|i| &self.catalogue[i])
|
||||
self.find(command).map(|i| &self.catalog[i])
|
||||
}
|
||||
|
||||
/// The effective bindings of a command; empty for an unknown one.
|
||||
@@ -492,9 +492,9 @@ impl Keymap {
|
||||
}
|
||||
|
||||
/// Whether bindings may use `scope`: the application, a scope registered in [`Scopes`], or a
|
||||
/// home or default scope of the catalogue.
|
||||
/// home or default scope of the catalog.
|
||||
pub fn knows_scope(&self, scope: &Scope) -> bool {
|
||||
scope.is_app() || self.scopes.knows(scope) || self.catalogue_scopes.contains(scope)
|
||||
scope.is_app() || self.scopes.knows(scope) || self.catalog_scopes.contains(scope)
|
||||
}
|
||||
|
||||
/// The command `chord` (pressed twice when `double`) runs with `active` focus.
|
||||
@@ -502,7 +502,7 @@ impl Keymap {
|
||||
let want = Trigger { chord: chord.effective(self.platform), double };
|
||||
let held = active.typing && !self.typing.fires(&want.chord);
|
||||
let Some(holders) = self.by_trigger.get(&want) else { return Lookup::None };
|
||||
// Each command's nearest rank; `holders` is in catalogue order.
|
||||
// Each command's nearest rank; `holders` is in catalog order.
|
||||
let mut ranks: Vec<(usize, u8)> = Vec::new();
|
||||
for &(i, k) in holders {
|
||||
let Some(r) = self.at(i, k).and_then(|(_, s)| self.scopes.rank(s, active)) else { continue };
|
||||
@@ -519,12 +519,12 @@ impl Keymap {
|
||||
let winners: Vec<usize> = ranks.iter().filter(|(_, r)| *r == best).map(|(i, _)| *i).collect();
|
||||
match winners.as_slice() {
|
||||
[] => Lookup::None,
|
||||
[i] => Lookup::Run(self.catalogue[*i].command.clone()),
|
||||
[i] => Lookup::Run(self.catalog[*i].command.clone()),
|
||||
[first, rest @ ..] => {
|
||||
if rest.iter().all(|j| self.same_group(*first, *j)) {
|
||||
Lookup::Cycle(winners.iter().map(|i| self.catalogue[*i].command.clone()).collect())
|
||||
Lookup::Cycle(winners.iter().map(|i| self.catalog[*i].command.clone()).collect())
|
||||
} else {
|
||||
Lookup::Run(self.catalogue[*first].command.clone())
|
||||
Lookup::Run(self.catalog[*first].command.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,7 +548,7 @@ impl Keymap {
|
||||
if bindings.len() > MAX_BINDINGS {
|
||||
return Err(AssignError::TooMany);
|
||||
}
|
||||
let home = self.catalogue[i].scope.clone();
|
||||
let home = self.catalog[i].scope.clone();
|
||||
let list = self.normalize(bindings, &home);
|
||||
let new: Vec<Bound> = list.iter().filter(|b| !self.current[i].iter().any(|c| self.same(c, b))).cloned().collect();
|
||||
self.apply(i, list, &new, policy)
|
||||
@@ -588,14 +588,14 @@ impl Keymap {
|
||||
}
|
||||
|
||||
/// Pairs of commands on one chord, at most [`MAX_CONFLICTS`], in chord order, then in
|
||||
/// catalogue order. Tools of one group and bindings of panels that are never active together
|
||||
/// catalog order. Tools of one group and bindings of panels that are never active together
|
||||
/// are left out; with panel fallthrough two unrelated panels on one chord tie in a third panel,
|
||||
/// which is reported as a [`ConflictKind::Shadow`].
|
||||
///
|
||||
/// When the cap cuts a chord's pairs, the pairs kept are the first found comparing the
|
||||
/// chord's holders grouped by scope and tool group, group by group in the order each group
|
||||
/// first appears in the catalogue; the kept pairs are still returned in chord order, then
|
||||
/// catalogue order.
|
||||
/// first appears in the catalog; the kept pairs are still returned in chord order, then
|
||||
/// catalog order.
|
||||
pub fn conflicts(&self) -> Vec<Conflict> {
|
||||
let mut chords: Vec<(&Trigger, &BTreeSet<(usize, usize)>)> = self.by_trigger.iter().filter(|(_, h)| h.len() > 1).collect();
|
||||
chords.sort_by_key(|(t, _)| **t);
|
||||
@@ -609,7 +609,7 @@ impl Keymap {
|
||||
}
|
||||
|
||||
/// The conflicts [`Keymap::conflicts`] reports for one command's bindings, with that command
|
||||
/// as `a`, in the order of its bindings, then catalogue order. Empty for an unknown command.
|
||||
/// as `a`, in the order of its bindings, then catalog order. Empty for an unknown command.
|
||||
pub fn conflicts_with(&self, command: &CommandRef) -> Vec<Conflict> {
|
||||
let Some(i) = self.find(command) else { return Vec::new() };
|
||||
let mut out = Vec::new();
|
||||
@@ -624,8 +624,8 @@ impl Keymap {
|
||||
let Some(clash) = self.conflict_relation(&a.1, &b.1) else { continue };
|
||||
out.push(Conflict {
|
||||
chord,
|
||||
a: (self.catalogue[i].command.clone(), self.public(i, a)),
|
||||
b: (self.catalogue[j].command.clone(), self.public(j, b)),
|
||||
a: (self.catalog[i].command.clone(), self.public(i, a)),
|
||||
b: (self.catalog[j].command.clone(), self.public(j, b)),
|
||||
kind: if clash { ConflictKind::Clash } else { ConflictKind::Shadow },
|
||||
in_defaults: self.defaults[i].contains(a) && self.defaults[j].contains(b),
|
||||
});
|
||||
@@ -647,7 +647,7 @@ impl Keymap {
|
||||
out.push(Problem::DuplicateKey { key: key.to_string() });
|
||||
continue;
|
||||
}
|
||||
let home = &self.catalogue[i].scope;
|
||||
let home = &self.catalog[i].scope;
|
||||
for text in texts {
|
||||
let (key, text) = (key.to_string(), text.clone());
|
||||
match Binding::parse(&text) {
|
||||
@@ -666,7 +666,7 @@ impl Keymap {
|
||||
pub(crate) fn clashing(&self, command: &CommandRef, bindings: &[Binding]) -> Vec<Holder> {
|
||||
let Some(i) = self.find(command) else { return Vec::new() };
|
||||
let new: Vec<Bound> = bindings.iter().map(|b| self.bound(i, b)).collect();
|
||||
self.clashes(i, &new).into_iter().map(|(j, b)| Holder { command: self.catalogue[j].command.clone(), binding: self.public(j, &b) }).collect()
|
||||
self.clashes(i, &new).into_iter().map(|(j, b)| Holder { command: self.catalog[j].command.clone(), binding: self.public(j, &b) }).collect()
|
||||
}
|
||||
|
||||
/// Whether binding `x` of command `a` and binding `y` of another command `b` clash, by the
|
||||
@@ -694,11 +694,11 @@ impl Keymap {
|
||||
}
|
||||
|
||||
fn bound(&self, i: usize, binding: &Binding) -> Bound {
|
||||
(binding.trigger, binding.scope_or(&self.catalogue[i].scope).clone())
|
||||
(binding.trigger, binding.scope_or(&self.catalog[i].scope).clone())
|
||||
}
|
||||
|
||||
fn public(&self, i: usize, (trigger, scope): &Bound) -> Binding {
|
||||
let scope = (*scope != self.catalogue[i].scope).then(|| scope.clone());
|
||||
let scope = (*scope != self.catalog[i].scope).then(|| scope.clone());
|
||||
Binding { trigger: *trigger, scope }
|
||||
}
|
||||
|
||||
@@ -724,7 +724,7 @@ impl Keymap {
|
||||
}
|
||||
|
||||
fn same_group(&self, i: usize, j: usize) -> bool {
|
||||
match (&self.catalogue[i].kind, &self.catalogue[j].kind) {
|
||||
match (&self.catalog[i].kind, &self.catalog[j].kind) {
|
||||
(Kind::Tool { group: a }, Kind::Tool { group: b }) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
@@ -774,7 +774,7 @@ impl Keymap {
|
||||
let mut group_of: HashMap<(&Scope, Option<&Option<String>>), usize> = HashMap::new();
|
||||
for (pos, &(i, k)) in holders.iter().enumerate() {
|
||||
let Some(b) = self.at(i, k) else { continue };
|
||||
let tools = match &self.catalogue[i].kind {
|
||||
let tools = match &self.catalog[i].kind {
|
||||
Kind::Tool { group } => Some(group),
|
||||
_ => None,
|
||||
};
|
||||
@@ -814,8 +814,8 @@ impl Keymap {
|
||||
for (_, _, clash, (i, a), (j, b)) in found {
|
||||
out.push(Conflict {
|
||||
chord,
|
||||
a: (self.catalogue[i].command.clone(), self.public(i, a)),
|
||||
b: (self.catalogue[j].command.clone(), self.public(j, b)),
|
||||
a: (self.catalog[i].command.clone(), self.public(i, a)),
|
||||
b: (self.catalog[j].command.clone(), self.public(j, b)),
|
||||
kind: if clash { ConflictKind::Clash } else { ConflictKind::Shadow },
|
||||
in_defaults: self.defaults[i].contains(a) && self.defaults[j].contains(b),
|
||||
});
|
||||
@@ -830,13 +830,13 @@ impl Keymap {
|
||||
if let Some((t, _)) = new.iter().find(|(t, _)| self.is_reserved(&t.chord)) {
|
||||
return Err(AssignError::Reserved(*t));
|
||||
}
|
||||
let home = &self.catalogue[i].scope;
|
||||
let home = &self.catalog[i].scope;
|
||||
if let Some((_, s)) = new.iter().find(|(_, s)| !self.usable_scope(s, home)) {
|
||||
return Err(AssignError::UnknownScope(s.clone()));
|
||||
}
|
||||
let clashes = self.clashes(i, new);
|
||||
let holders: Vec<Holder> =
|
||||
clashes.iter().map(|(j, b)| Holder { command: self.catalogue[*j].command.clone(), binding: self.public(*j, b) }).collect();
|
||||
clashes.iter().map(|(j, b)| Holder { command: self.catalog[*j].command.clone(), binding: self.public(*j, b) }).collect();
|
||||
let mut done = Assigned::default();
|
||||
let mut writes: Vec<(usize, Vec<Bound>)> = Vec::new();
|
||||
match policy {
|
||||
@@ -890,7 +890,7 @@ impl Keymap {
|
||||
kept.truncate(MAX_BINDINGS.saturating_sub(list.len()));
|
||||
self.remove_entries(i);
|
||||
if !kept.is_empty() || !self.is_default(i, &list) {
|
||||
let home = &self.catalogue[i].scope;
|
||||
let home = &self.catalog[i].scope;
|
||||
let mut texts: Vec<String> = list.iter().map(|b| self.public(i, b).text(home)).collect();
|
||||
texts.extend(kept);
|
||||
if self.overrides.insert(self.keys[i].clone(), texts) {
|
||||
@@ -930,7 +930,7 @@ impl Keymap {
|
||||
/// version, a scope this app lacks, a chord this platform reserves.
|
||||
fn unusable_texts(&self, i: usize) -> Vec<String> {
|
||||
let Some(texts) = self.stored[i].as_deref().and_then(|key| self.overrides.get(key)) else { return Vec::new() };
|
||||
let home = &self.catalogue[i].scope;
|
||||
let home = &self.catalog[i].scope;
|
||||
texts.iter().filter(|t| !Binding::parse(t).is_ok_and(|b| self.usable(&b, home))).cloned().collect()
|
||||
}
|
||||
|
||||
@@ -985,7 +985,7 @@ impl Keymap {
|
||||
if !taken.insert(i) {
|
||||
continue;
|
||||
}
|
||||
let home = &self.catalogue[i].scope;
|
||||
let home = &self.catalog[i].scope;
|
||||
let usable = texts.iter().filter_map(|t| Binding::parse(t).ok()).filter(|b| self.usable(b, home));
|
||||
let list = self.normalize(usable, home);
|
||||
// An entry none of whose texts is usable here (a key name from a newer version, a scope
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! [`chord`] holds the key and modifier types, the canonical text form that settings and control
|
||||
//! protocols store, display text per platform, and a reader for other applications' key text.
|
||||
//! [`command`] names what a binding runs, [`scope`] where it works, and [`keymap`] holds an app's
|
||||
//! catalogue with default bindings, the user's [`Overrides`], dispatch ([`Keymap::lookup`]),
|
||||
//! catalog with default bindings, the user's [`Overrides`], dispatch ([`Keymap::lookup`]),
|
||||
//! editing and conflicts. [`profiles`] keeps named sets of overrides; [`reserved`] lists the
|
||||
//! chords each operating system takes, and a keymap reserves no chord unless the app opts in with
|
||||
//! [`Keymap::with_reserved`], e.g. `with_reserved(reserved::system(platform))`. [`import`] brings
|
||||
@@ -23,7 +23,7 @@
|
||||
//! assert_eq!(chord.display(Platform::Windows), "Ctrl+Shift+Z");
|
||||
//! ```
|
||||
//!
|
||||
//! # A catalogue and dispatch
|
||||
//! # A catalog and dispatch
|
||||
//!
|
||||
//! The app lists every command it can bind. A command with parameters has the key
|
||||
//! `window.panel {"panel":"layers"}` in settings and reports. Tools of one group share a key and
|
||||
@@ -36,7 +36,7 @@
|
||||
//! let key = |text: &str| Binding::parse(text).unwrap();
|
||||
//! let layers = CommandRef::with_params("window.panel", json!({"panel": "layers"}));
|
||||
//! let select = Kind::Tool { group: Some("select".into()) };
|
||||
//! let catalogue = vec![
|
||||
//! let catalog = vec![
|
||||
//! Bindable::new("file.save", "Save", Scope::app()).with_path(vec!["File".into(), "Save".into()]).with_default(key("Cmd+S")),
|
||||
//! Bindable::new(layers.clone(), "Layers", Scope::app()).with_path(vec!["Window".into(), "Layers".into()]).with_default(key("F7")),
|
||||
//! Bindable::new("tool.marquee", "Marquee Tool", Scope::app()).with_kind(select.clone()).with_default(key("M")),
|
||||
@@ -47,7 +47,7 @@
|
||||
//! // An app command whose binding works only in the Text context.
|
||||
//! Bindable::new("type.bold", "Bold", Scope::app()).with_default(key("Text: Cmd+B")),
|
||||
//! ];
|
||||
//! let keymap = Keymap::new(catalogue, Scopes::new().with_panel("Timeline").with_context("Text"), Platform::Mac);
|
||||
//! let keymap = Keymap::new(catalog, Scopes::new().with_panel("Timeline").with_context("Text"), Platform::Mac);
|
||||
//! assert_eq!(layers.key(), r#"window.panel {"panel":"layers"}"#);
|
||||
//!
|
||||
//! // Dispatch: the chord pressed, whether it is a double press, and what has the keyboard.
|
||||
@@ -89,9 +89,9 @@
|
||||
//! const SCHEMA_VERSION: u32 = 1;
|
||||
//! }
|
||||
//!
|
||||
//! # let catalogue = Vec::new();
|
||||
//! # let catalog = Vec::new();
|
||||
//! let (mut store, _report) = SettingsStore::<Prefs>::open(Location::Auto);
|
||||
//! let mut keymap = Keymap::new(catalogue, Scopes::new(), Platform::current()).with_overrides(store.get().shortcuts.clone());
|
||||
//! let mut keymap = Keymap::new(catalog, Scopes::new(), Platform::current()).with_overrides(store.get().shortcuts.clone());
|
||||
//! // Entries for unknown commands and texts that do not parse are kept and listed here.
|
||||
//! let _problems = keymap.problems();
|
||||
//!
|
||||
|
||||
@@ -78,7 +78,7 @@ const CTRL: Modifiers = Modifiers { ctrl: true, command: true, ..Modifiers::NONE
|
||||
fn dispatch_keymap() -> Keymap {
|
||||
let app = |id: &str, keys: &[&str]| keys.iter().fold(Bindable::new(id, id, Scope::app()), |e, k| e.with_default(Binding::parse(k).unwrap()));
|
||||
let tool = |id: &str| app(id, &["M"]).with_kind(Kind::Tool { group: Some("select".into()) });
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
app("edit.copy", &["Cmd+C", "Cmd+Insert"]),
|
||||
app("edit.paste", &["Cmd+V", "Shift+Insert"]),
|
||||
app("app.quit", &["Cmd+Alt+Q"]),
|
||||
@@ -94,7 +94,7 @@ fn dispatch_keymap() -> Keymap {
|
||||
app("reveal.one", &["U"]),
|
||||
app("reveal.all", &["U U"]),
|
||||
];
|
||||
Keymap::new(catalogue, Scopes::new().with_panel("Timeline"), Platform::Windows)
|
||||
Keymap::new(catalog, Scopes::new().with_panel("Timeline"), Platform::Windows)
|
||||
}
|
||||
|
||||
fn press(key: EguiKey, modifiers: Modifiers) -> Event {
|
||||
|
||||
@@ -34,7 +34,7 @@ fn panel(name: &str) -> CommandRef {
|
||||
CommandRef::with_params("window.panel", json!({ "panel": name }))
|
||||
}
|
||||
|
||||
fn catalogue() -> Vec<Bindable> {
|
||||
fn catalog() -> Vec<Bindable> {
|
||||
vec![
|
||||
command("file.new", "Document…", &["File", "New", "Document…"], &["Cmd+N"]),
|
||||
command("dialog.newDocument", "New Document Dialog", &[], &["Cmd+N"]),
|
||||
@@ -65,7 +65,7 @@ fn catalogue() -> Vec<Bindable> {
|
||||
}
|
||||
|
||||
fn keymap(platform: Platform) -> Keymap {
|
||||
Keymap::new(catalogue(), Scopes::new().with_context("Text"), platform).with_reserved(reserved::system(platform))
|
||||
Keymap::new(catalog(), Scopes::new().with_context("Text"), platform).with_reserved(reserved::system(platform))
|
||||
}
|
||||
|
||||
fn key(flavor: Flavor, context: &str, text: &str) -> ForeignKey {
|
||||
@@ -139,7 +139,7 @@ fn has(km: &Keymap, id: impl Into<CommandRef>, texts: &[&str]) {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labels_normalise_for_matching() {
|
||||
fn labels_normalize_for_matching() {
|
||||
assert_eq!(normalize_label("Check Spelling..."), "check spelling");
|
||||
assert_eq!(normalize_label("Check Spelling…"), "check spelling");
|
||||
assert_eq!(normalize_label("Grids && Guides"), "grids guides");
|
||||
@@ -152,7 +152,7 @@ fn labels_normalise_for_matching() {
|
||||
fn every_match_step_finds_its_command() {
|
||||
let (km, r) = import_windows();
|
||||
assert_eq!(r.set.as_deref(), Some("My Set"));
|
||||
// 1: full path, with `&`, case and spacing normalised, and Show/Hide dropped for a toggle.
|
||||
// 1: full path, with `&`, case and spacing normalized, and Show/Hide dropped for a toggle.
|
||||
has(&km, "view.guides", &["Cmd+Alt+;"]);
|
||||
// 2: joined segments `Text Frame Options` in Object.
|
||||
has(&km, "object.textFrameOptions", &["Cmd+Alt+B"]);
|
||||
@@ -463,12 +463,12 @@ fn hostile_sets_are_capped_and_counted() {
|
||||
#[test]
|
||||
#[ignore = "worst-case timing; run with --ignored"]
|
||||
fn hostile_sets_finish_in_time() {
|
||||
// A full catalogue whose labels share words with every unmatched entry below, so each
|
||||
// A full catalog whose labels share words with every unmatched entry below, so each
|
||||
// suggestion scores every command.
|
||||
let mut catalogue = catalogue();
|
||||
let filler = MAX_COMMANDS - catalogue.len();
|
||||
catalogue.extend((0..filler).map(|i| command(format!("filler.c{i}").as_str(), &format!("Filler Command {i}"), &[], &[])));
|
||||
let mut km = Keymap::new(catalogue, Scopes::new().with_context("Text"), Platform::Windows).with_reserved(reserved::system(Platform::Windows));
|
||||
let mut catalog = catalog();
|
||||
let filler = MAX_COMMANDS - catalog.len();
|
||||
catalog.extend((0..filler).map(|i| command(format!("filler.c{i}").as_str(), &format!("Filler Command {i}"), &[], &[])));
|
||||
let mut km = Keymap::new(catalog, Scopes::new().with_context("Text"), Platform::Windows).with_reserved(reserved::system(Platform::Windows));
|
||||
let long: String = "ä€𝄞 x".repeat(MAX_IMPORT_TEXT);
|
||||
// Odd entries have keys and long labels that match nothing, so they reach the suggestions:
|
||||
// words in the whole label, or two words and symbols, which score high enough to suggest.
|
||||
@@ -593,8 +593,8 @@ fn report_json_uses_camel_case_names() {
|
||||
assert_eq!(v["changed"][0]["from"], json!(["Cmd+W"]));
|
||||
}
|
||||
|
||||
fn import_into(catalogue: Vec<Bindable>, entries: Vec<ForeignEntry>, options: &ImportOptions) -> (Keymap, ImportReport) {
|
||||
let mut km = Keymap::new(catalogue, Scopes::new(), Platform::Windows);
|
||||
fn import_into(catalog: Vec<Bindable>, entries: Vec<ForeignEntry>, options: &ImportOptions) -> (Keymap, ImportReport) {
|
||||
let mut km = Keymap::new(catalog, Scopes::new(), Platform::Windows);
|
||||
let report = plan(&ForeignSet { name: None, entries }, &km, options).apply(&mut km);
|
||||
(km, report)
|
||||
}
|
||||
@@ -609,7 +609,7 @@ fn unmatched(r: &ImportReport) -> Vec<(&str, UnmatchedReason)> {
|
||||
|
||||
#[test]
|
||||
fn panel_menu_entries_match_by_path_end_or_label() {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
command("type.allCaps", "All Caps", &["Panels", "Character", "All Caps"], &[]),
|
||||
command("pages.overrideAll", "Override All Parent Items", &["Pages", "Override All Parent Page Items"], &[]),
|
||||
command("paragraph.justification", "Justification…", &[], &[]),
|
||||
@@ -637,10 +637,10 @@ fn panel_menu_entries_match_by_path_end_or_label() {
|
||||
panel_entry(&["Character", "Small Caps"], "Ctrl+F5"),
|
||||
// No path end fits and the label alone fits two commands.
|
||||
panel_entry(&["Color", "New Swatch"], "Ctrl+F6"),
|
||||
// Without the mark, a path with no menu matches only a whole catalogue path.
|
||||
// Without the mark, a path with no menu matches only a whole catalog path.
|
||||
entry(None, &["Character", "All Caps"], &[(d, "Ctrl+F7")], Flavor::Windows),
|
||||
];
|
||||
let (km, r) = import_into(catalogue, entries, &default_context());
|
||||
let (km, r) = import_into(catalog, entries, &default_context());
|
||||
has(&km, "type.allCaps", &["Cmd+F1"]);
|
||||
has(&km, "pages.overrideAll", &["Cmd+F2"]);
|
||||
has(&km, "paragraph.justification", &["Cmd+F3"]);
|
||||
@@ -659,7 +659,7 @@ fn panel_menu_entries_match_by_path_end_or_label() {
|
||||
|
||||
#[test]
|
||||
fn aliases_match_like_labels() {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
command("app.quit", "Quit DesignCraft", &["File", "Quit DesignCraft"], &[]).with_alias("Exit"),
|
||||
command("app.preferences", "Preferences…", &["Edit", "Preferences…"], &[]).with_alias("Preferences: General…"),
|
||||
command("type.bold", "Bold", &["Type", "Style", "Bold"], &[]).with_alias("Apply bold"),
|
||||
@@ -672,7 +672,7 @@ fn aliases_match_like_labels() {
|
||||
entry(None, &["Apply Bold"], &[(d, "Ctrl+Shift+B")], Flavor::Windows),
|
||||
win("Bearbeiten", &["Einfügen"], &[(d, "Ctrl+Shift+V")]),
|
||||
];
|
||||
let (km, r) = import_into(catalogue, entries, &default_context());
|
||||
let (km, r) = import_into(catalog, entries, &default_context());
|
||||
has(&km, "app.quit", &["Cmd+Q"]);
|
||||
has(&km, "app.preferences", &["Cmd+K"]);
|
||||
has(&km, "type.bold", &["Cmd+Shift+B"]);
|
||||
@@ -682,7 +682,7 @@ fn aliases_match_like_labels() {
|
||||
|
||||
#[test]
|
||||
fn show_and_hide_are_read_off_only_for_toggles() {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
command(panel("conveyor"), "Conveyor", &["Window", "Conveyor"], &[]),
|
||||
command("view.rulers", "Rulers", &["View", "Rulers"], &[]).toggle(),
|
||||
command("view.frameEdges", "Show Frame Edges", &["View", "Extras", "Show Frame Edges"], &[]).toggle(),
|
||||
@@ -700,7 +700,7 @@ fn show_and_hide_are_read_off_only_for_toggles() {
|
||||
win("Type", &["Show Hidden Characters"], &[(d, "Ctrl+Alt+I")]),
|
||||
win("Type", &["Hide Hidden Characters"], &[(d, "Ctrl+Alt+H")]),
|
||||
];
|
||||
let (km, r) = import_into(catalogue, entries, &default_context());
|
||||
let (km, r) = import_into(catalog, entries, &default_context());
|
||||
has(&km, panel("conveyor"), &[]);
|
||||
has(&km, "view.rulers", &["Cmd+R", "Cmd+Shift+R"]);
|
||||
has(&km, "view.frameEdges", &["Cmd+H"]);
|
||||
@@ -713,7 +713,7 @@ fn show_and_hide_are_read_off_only_for_toggles() {
|
||||
|
||||
#[test]
|
||||
fn only_entries_with_keys_to_bind_count_toward_suggestions() {
|
||||
let catalogue = vec![command("edit.checkSpelling", "Check Spelling…", &["Edit", "Check Spelling…"], &[])];
|
||||
let catalog = vec![command("edit.checkSpelling", "Check Spelling…", &["Edit", "Check Spelling…"], &[])];
|
||||
let d = "Default";
|
||||
// Twice the cap of unmatched entries without keys, or with keys only in a skipped context,
|
||||
// before one with a key.
|
||||
@@ -726,7 +726,7 @@ fn only_entries_with_keys_to_bind_count_toward_suggestions() {
|
||||
suggest: true,
|
||||
..ImportOptions::default()
|
||||
};
|
||||
let (_, r) = import_into(catalogue, entries, &options);
|
||||
let (_, r) = import_into(catalog, entries, &options);
|
||||
assert_eq!(r.unmatched_without_shortcut, 2 * MAX_SUGGESTIONS);
|
||||
assert_eq!(r.unmatched.len(), 1);
|
||||
assert_eq!(r.unmatched[0].keys, "Default: Ctrl+K", "keys in a skipped context are left out");
|
||||
@@ -735,7 +735,7 @@ fn only_entries_with_keys_to_bind_count_toward_suggestions() {
|
||||
assert_eq!(r.truncated, 0);
|
||||
}
|
||||
|
||||
fn pages_catalogue() -> Vec<Bindable> {
|
||||
fn pages_catalog() -> Vec<Bindable> {
|
||||
vec![
|
||||
command("app.newDocumentDialog", "New Document…", &["File", "New Document…"], &["Cmd+N"]),
|
||||
command("layout.pages.insert", "Insert Pages…", &["Layout", "Pages", "Insert Pages…"], &["Cmd+Shift+P"]),
|
||||
@@ -754,7 +754,7 @@ fn panel_menu_entries_without_keys_change_nothing() {
|
||||
// The same for the last segment alone, with a panel the app has.
|
||||
none(&["Pages", "New Document"]),
|
||||
];
|
||||
let (km, r) = import_into(pages_catalogue(), entries, &default_context());
|
||||
let (km, r) = import_into(pages_catalog(), entries, &default_context());
|
||||
has(&km, "app.newDocumentDialog", &["Cmd+N"]);
|
||||
has(&km, "layout.pages.insert", &["Cmd+Shift+P"]);
|
||||
assert!(r.changed.is_empty(), "{:?}", r.changed);
|
||||
@@ -781,7 +781,7 @@ fn an_entry_without_keys_never_clears_keys_another_entry_gives() {
|
||||
let keyed = || win("Layout", &["Pages", "Insert Pages..."], &[(d, "Ctrl+Alt+P")]);
|
||||
let keyless = || win("Layout", &["Insert Pages..."], &[]);
|
||||
for entries in [vec![keyed(), keyless()], vec![keyless(), keyed()]] {
|
||||
let (km, _) = import_into(pages_catalogue(), entries, &default_context());
|
||||
let (km, _) = import_into(pages_catalog(), entries, &default_context());
|
||||
has(&km, "layout.pages.insert", &["Cmd+Alt+P"]);
|
||||
}
|
||||
|
||||
@@ -790,14 +790,14 @@ fn an_entry_without_keys_never_clears_keys_another_entry_gives() {
|
||||
ForeignEntry::new(None, ["Pages", "Insert Pages..."]).in_panel_menu(),
|
||||
win("Layout", &["Pages", "Insert Pages..."], &[(d, "Ctrl+Shift+P")]),
|
||||
];
|
||||
let (km, r) = import_into(pages_catalogue(), entries, &default_context());
|
||||
let (km, r) = import_into(pages_catalog(), entries, &default_context());
|
||||
has(&km, "layout.pages.insert", &["Cmd+Shift+P"]);
|
||||
assert_eq!(r.matched_without_change.len(), 1);
|
||||
|
||||
// The later entry still takes the chord from another command; the entry without keys does
|
||||
// not clear the command that lost it.
|
||||
let entries = vec![keyless(), keyed(), win("File", &["Close"], &[(d, "Ctrl+Alt+P")])];
|
||||
let (km, r) = import_into(pages_catalogue(), entries, &default_context());
|
||||
let (km, r) = import_into(pages_catalog(), entries, &default_context());
|
||||
has(&km, "file.close", &["Cmd+Alt+P"]);
|
||||
has(&km, "layout.pages.insert", &["Cmd+Shift+P"]);
|
||||
assert_eq!(r.skipped.iter().map(|s| (s.command.id.as_str(), s.reason)).collect::<Vec<_>>(), [("layout.pages.insert", SkipReason::Taken)]);
|
||||
|
||||
@@ -54,14 +54,14 @@ fn command_keys_are_canonical_whatever_the_map_order() {
|
||||
|
||||
#[test]
|
||||
fn lookup_picks_the_nearest_scope() {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
app("edit.copy", &["Cmd+C"]),
|
||||
in_scope("timeline.copy", "Timeline", &["Cmd+C"]),
|
||||
app("file.bake", &["Cmd+B"]),
|
||||
in_scope("text.bold", "Text", &["Cmd+B", "Cmd+I"]),
|
||||
in_scope("timeline.loop", "Timeline", &["L"]),
|
||||
];
|
||||
let km = Keymap::new(catalogue.clone(), scopes(), Platform::Mac);
|
||||
let km = Keymap::new(catalog.clone(), scopes(), Platform::Mac);
|
||||
let text = ["Text"];
|
||||
assert_eq!(press(&km, "Cmd+C", &Active::NONE), run("edit.copy"));
|
||||
assert_eq!(press(&km, "Cmd+C", &Active::panel("Timeline")), run("timeline.copy"));
|
||||
@@ -73,14 +73,14 @@ fn lookup_picks_the_nearest_scope() {
|
||||
assert_eq!(press(&km, "L", &Active::panel("Effects")), run("timeline.loop"), "fallthrough");
|
||||
assert_eq!(km.lookup(&chord("L"), true, &Active::panel("Timeline")), Lookup::None, "double press is its own trigger");
|
||||
|
||||
let strict = Keymap::new(catalogue, scopes().with_panel_fallthrough(false), Platform::Mac);
|
||||
let strict = Keymap::new(catalog, scopes().with_panel_fallthrough(false), Platform::Mac);
|
||||
assert_eq!(press(&strict, "L", &Active::panel("Effects")), Lookup::None);
|
||||
assert_eq!(press(&strict, "L", &Active::panel("Timeline")), run("timeline.loop"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_rules_decide_what_fires_in_a_text_field() {
|
||||
let catalogue = vec![app("tool.brush", &["B"]), app("edit.copy", &["Cmd+C"]), app("help", &["F1"]), app("view.alt", &["Alt+V"])];
|
||||
let catalog = vec![app("tool.brush", &["B"]), app("edit.copy", &["Cmd+C"]), app("help", &["F1"]), app("view.alt", &["Alt+V"])];
|
||||
let typing = Active { panel: None, contexts: &[], typing: true };
|
||||
let cases = [
|
||||
(TypingRule::PlainKeysWait, [false, true, true, true]),
|
||||
@@ -88,7 +88,7 @@ fn typing_rules_decide_what_fires_in_a_text_field() {
|
||||
(TypingRule::AllWait, [false, false, false, false]),
|
||||
];
|
||||
for (rule, fires) in cases {
|
||||
let km = Keymap::new(catalogue.clone(), Scopes::new(), Platform::Windows).with_typing_rule(rule);
|
||||
let km = Keymap::new(catalog.clone(), Scopes::new(), Platform::Windows).with_typing_rule(rule);
|
||||
for ((key, id), fire) in [("B", "tool.brush"), ("Cmd+C", "edit.copy"), ("F1", "help"), ("Alt+V", "view.alt")].into_iter().zip(fires) {
|
||||
let want = if fire { run(id) } else { Lookup::None };
|
||||
assert_eq!(press(&km, key, &typing), want, "{rule:?} {key}");
|
||||
@@ -99,7 +99,7 @@ fn typing_rules_decide_what_fires_in_a_text_field() {
|
||||
|
||||
#[test]
|
||||
fn bindings_of_an_active_context_fire_while_typing_under_every_rule() {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
app("text.bold", &["Text: Cmd+B", "Text: B"]),
|
||||
app("file.bake", &["Cmd+B"]),
|
||||
in_scope("timeline.cut", "Timeline", &["Cmd+K"]),
|
||||
@@ -107,7 +107,7 @@ fn bindings_of_an_active_context_fire_while_typing_under_every_rule() {
|
||||
];
|
||||
let editing = Active { panel: Some("Timeline"), contexts: &["Text"], typing: true };
|
||||
let elsewhere = Active { contexts: &[], ..editing };
|
||||
let km = Keymap::new(catalogue, scopes(), Platform::Mac).with_typing_rule(TypingRule::AllWait);
|
||||
let km = Keymap::new(catalog, scopes(), Platform::Mac).with_typing_rule(TypingRule::AllWait);
|
||||
assert_eq!(press(&km, "Cmd+B", &editing), run("text.bold"));
|
||||
assert_eq!(press(&km, "B", &editing), run("text.bold"));
|
||||
assert_eq!(press(&km, "Cmd+K", &editing), Lookup::None, "panel bindings wait");
|
||||
@@ -134,8 +134,8 @@ fn tools_of_one_group_cycle_and_keep_their_shared_key() {
|
||||
|
||||
#[test]
|
||||
fn steal_keep_and_refuse() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), app("edit.duplicate", &["Cmd+D"]), in_scope("timeline.copy", "Timeline", &[])];
|
||||
let mut km = Keymap::new(catalogue, scopes(), Platform::Mac);
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), app("edit.duplicate", &["Cmd+D"]), in_scope("timeline.copy", "Timeline", &[])];
|
||||
let mut km = Keymap::new(catalog, scopes(), Platform::Mac);
|
||||
let dup = cmd("edit.duplicate");
|
||||
|
||||
let err = km.assign(&dup, b("Cmd+C"), Policy::Refuse).unwrap_err();
|
||||
@@ -174,8 +174,8 @@ fn twins_keep_their_shared_default_key() {
|
||||
#[test]
|
||||
fn clearing_a_command_frees_its_panel_copies() {
|
||||
// filmcraft#1333: New Bin holds Cmd+B in the application and in the Project panel.
|
||||
let catalogue = vec![app("project.newBin", &["Cmd+B", "Project: Cmd+B"]), app("clip.bold", &[])];
|
||||
let mut km = Keymap::new(catalogue, Scopes::new().with_panel("Project"), Platform::Mac);
|
||||
let catalog = vec![app("project.newBin", &["Cmd+B", "Project: Cmd+B"]), app("clip.bold", &[])];
|
||||
let mut km = Keymap::new(catalog, Scopes::new().with_panel("Project"), Platform::Mac);
|
||||
assert_eq!(press(&km, "Cmd+B", &Active::panel("Project")), run("project.newBin"));
|
||||
assert!(km.clear(&cmd("project.newBin")));
|
||||
assert!(km.bindings(&cmd("project.newBin")).is_empty());
|
||||
@@ -185,25 +185,25 @@ fn clearing_a_command_frees_its_panel_copies() {
|
||||
|
||||
#[test]
|
||||
fn reserved_chords_are_refused() {
|
||||
let catalogue = vec![app("view.next", &[])];
|
||||
let mut mac = Keymap::new(catalogue.clone(), Scopes::new(), Platform::Mac).with_reserved(reserved::system(Platform::Mac));
|
||||
let catalog = vec![app("view.next", &[])];
|
||||
let mut mac = Keymap::new(catalog.clone(), Scopes::new(), Platform::Mac).with_reserved(reserved::system(Platform::Mac));
|
||||
let err = mac.assign(&cmd("view.next"), b("Cmd+Tab"), Policy::Steal).unwrap_err();
|
||||
assert_eq!(err, AssignError::Reserved(Trigger::parse("Cmd+Tab").unwrap()));
|
||||
|
||||
let mut win = Keymap::new(catalogue.clone(), Scopes::new(), Platform::Windows).with_reserved(reserved::system(Platform::Windows));
|
||||
let mut win = Keymap::new(catalog.clone(), Scopes::new(), Platform::Windows).with_reserved(reserved::system(Platform::Windows));
|
||||
assert!(matches!(win.assign(&cmd("view.next"), b("Ctrl+Alt+Delete"), Policy::Steal), Err(AssignError::Reserved(_))));
|
||||
assert!(reserved::system(Platform::Linux).contains(&chord("Cmd+Alt+F12")));
|
||||
|
||||
let mut o = Overrides::new();
|
||||
o.insert("view.next", vec!["Alt+F4".into()]);
|
||||
let linux = Keymap::new(catalogue, Scopes::new(), Platform::Linux).with_reserved(reserved::system(Platform::Linux)).with_overrides(o);
|
||||
let linux = Keymap::new(catalog, Scopes::new(), Platform::Linux).with_reserved(reserved::system(Platform::Linux)).with_overrides(o);
|
||||
assert!(linux.bindings(&cmd("view.next")).is_empty());
|
||||
assert_eq!(linux.problems(), vec![Problem::Reserved { key: "view.next".into(), text: "Alt+F4".into() }]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflicts_tell_clashes_from_shadows() {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
app("edit.copy", &["Cmd+C"]),
|
||||
app("color.copy", &["Cmd+C"]),
|
||||
in_scope("timeline.copy", "Timeline", &["Cmd+C"]),
|
||||
@@ -214,7 +214,7 @@ fn conflicts_tell_clashes_from_shadows() {
|
||||
in_scope("render.find", "Render", &["Cmd+R"]),
|
||||
in_scope("effects.render", "Effects", &["Cmd+R"]),
|
||||
];
|
||||
let mut km = Keymap::new(catalogue, scopes(), Platform::Mac);
|
||||
let mut km = Keymap::new(catalog, scopes(), Platform::Mac);
|
||||
let summary = |km: &Keymap| -> Vec<(String, String, String, ConflictKind, bool)> {
|
||||
km.conflicts().into_iter().map(|c| (c.chord.to_string(), c.a.0.id, c.b.0.id, c.kind, c.in_defaults)).collect()
|
||||
};
|
||||
@@ -254,7 +254,7 @@ fn conflicts_tell_clashes_from_shadows() {
|
||||
assert_eq!(with("edit.copy"), [w("Cmd+C", "color.copy", ConflictKind::Clash), w("Cmd+C", "timeline.copy", ConflictKind::Shadow)]);
|
||||
assert_eq!(with("edit.cut"), [w("Cmd+X", "text.cut", ConflictKind::Shadow), w("Cmd+D", "edit.find", ConflictKind::Clash)]);
|
||||
assert!(with("nope").is_empty());
|
||||
for entry in km.catalogue() {
|
||||
for entry in km.catalog() {
|
||||
let id = entry.command.id.as_str();
|
||||
let mut direct: Vec<_> =
|
||||
km.conflicts_with(&entry.command).into_iter().map(|c| (c.chord, c.a.0.id, c.b.0.id, c.kind, c.in_defaults)).collect();
|
||||
@@ -275,18 +275,18 @@ fn conflicts_tell_clashes_from_shadows() {
|
||||
assert_eq!(direct, filtered, "{id}");
|
||||
}
|
||||
|
||||
let strict = Keymap::new(km.catalogue().to_vec(), scopes().with_panel_fallthrough(false), Platform::Mac);
|
||||
let strict = Keymap::new(km.catalog().to_vec(), scopes().with_panel_fallthrough(false), Platform::Mac);
|
||||
assert!(!strict.conflicts().iter().any(|c| c.chord.to_string() == "Cmd+R"));
|
||||
}
|
||||
|
||||
/// `commands` commands, each with 16 bindings on one chord in 16 panels.
|
||||
fn crowded(commands: usize, scopes: Scopes, kind: Kind) -> (Keymap, Overrides) {
|
||||
let catalogue: Vec<Bindable> = (0..commands).map(|n| app(&format!("c.{n}"), &[]).with_kind(kind.clone())).collect();
|
||||
let catalog: Vec<Bindable> = (0..commands).map(|n| app(&format!("c.{n}"), &[]).with_kind(kind.clone())).collect();
|
||||
let mut o = Overrides::new();
|
||||
for n in 0..commands {
|
||||
assert!(o.insert(format!("c.{n}"), (1..=MAX_BINDINGS).map(|p| format!("P{p}: K")).collect()));
|
||||
}
|
||||
(Keymap::new(catalogue, scopes, Platform::Mac), o)
|
||||
(Keymap::new(catalog, scopes, Platform::Mac), o)
|
||||
}
|
||||
|
||||
fn panels(n: usize) -> Scopes {
|
||||
@@ -344,8 +344,8 @@ fn crowded_chords_stay_fast() {
|
||||
|
||||
#[test]
|
||||
fn assigning_many_commands_stays_fast() {
|
||||
let catalogue: Vec<Bindable> = (0..MAX_COMMANDS).map(|n| app(&format!("c.{n}"), &[])).collect();
|
||||
let mut km = Keymap::new(catalogue, panels(MAX_BINDINGS), Platform::Mac);
|
||||
let catalog: Vec<Bindable> = (0..MAX_COMMANDS).map(|n| app(&format!("c.{n}"), &[])).collect();
|
||||
let mut km = Keymap::new(catalog, panels(MAX_BINDINGS), Platform::Mac);
|
||||
let list: Vec<Binding> = (1..=MAX_BINDINGS).map(|p| b(&format!("P{p}: K"))).collect();
|
||||
let t = Instant::now();
|
||||
for n in 0..MAX_COMMANDS {
|
||||
@@ -377,14 +377,14 @@ fn binding_text_round_trips_for_every_key() {
|
||||
|
||||
#[test]
|
||||
fn edits_are_refused_when_the_overrides_are_full() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), app("edit.duplicate", &["Cmd+D"]), app("edit.cut", &[])];
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), app("edit.duplicate", &["Cmd+D"]), app("edit.cut", &[])];
|
||||
let mut o = Overrides::new();
|
||||
o.insert("edit.cut", vec!["Cmd+X".into()]);
|
||||
for n in 1..MAX_COMMANDS {
|
||||
o.insert(format!("junk.{n}"), vec![]);
|
||||
}
|
||||
assert_eq!(o.len(), MAX_COMMANDS);
|
||||
let mut km = Keymap::new(catalogue, Scopes::new(), Platform::Mac).with_overrides(o.clone());
|
||||
let mut km = Keymap::new(catalog, Scopes::new(), Platform::Mac).with_overrides(o.clone());
|
||||
let (copy, dup, cut) = (cmd("edit.copy"), cmd("edit.duplicate"), cmd("edit.cut"));
|
||||
|
||||
assert_eq!(km.assign(&dup, b("Cmd+K"), Policy::Steal), Err(AssignError::Full));
|
||||
@@ -405,11 +405,11 @@ fn edits_are_refused_when_the_overrides_are_full() {
|
||||
|
||||
#[test]
|
||||
fn bindings_in_unknown_scopes_are_refused() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), in_scope("timeline.mark", "Timeline", &["M"])];
|
||||
let mut km = Keymap::new(catalogue, Scopes::new().with_context("Text"), Platform::Mac);
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), in_scope("timeline.mark", "Timeline", &["M"])];
|
||||
let mut km = Keymap::new(catalog, Scopes::new().with_context("Text"), Platform::Mac);
|
||||
let copy = cmd("edit.copy");
|
||||
assert_eq!(km.assign(©, b("Nowhere: Cmd+K"), Policy::Steal), Err(AssignError::UnknownScope(scope("Nowhere"))));
|
||||
assert!(km.assign(©, b("Timeline: Cmd+K"), Policy::Steal).is_ok(), "a catalogue scope");
|
||||
assert!(km.assign(©, b("Timeline: Cmd+K"), Policy::Steal).is_ok(), "a catalog scope");
|
||||
assert!(km.assign(©, b("Text: Cmd+K"), Policy::Steal).is_ok(), "a registered scope");
|
||||
|
||||
let mut o = Overrides::new();
|
||||
@@ -421,11 +421,11 @@ fn bindings_in_unknown_scopes_are_refused() {
|
||||
|
||||
#[test]
|
||||
fn unreadable_entries_keep_the_defaults() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), app("edit.paste", &["Cmd+V"])];
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), app("edit.paste", &["Cmd+V"])];
|
||||
let mut o = Overrides::new();
|
||||
o.insert("edit.copy", vec!["Cmd+Nope".into()]);
|
||||
o.insert("edit.paste", vec![]);
|
||||
let mut km = Keymap::new(catalogue, Scopes::new(), Platform::Mac).with_overrides(o);
|
||||
let mut km = Keymap::new(catalog, Scopes::new(), Platform::Mac).with_overrides(o);
|
||||
assert_eq!(km.bindings(&cmd("edit.copy")), vec![b("Cmd+C")]);
|
||||
assert!(km.bindings(&cmd("edit.paste")).is_empty());
|
||||
assert_eq!(km.overrides().get("edit.copy"), Some(&["Cmd+Nope".to_string()][..]), "verbatim");
|
||||
@@ -435,13 +435,13 @@ fn unreadable_entries_keep_the_defaults() {
|
||||
|
||||
#[test]
|
||||
fn entries_with_nothing_usable_keep_the_defaults() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), app("edit.cut", &["Cmd+X"]), app("edit.paste", &["Cmd+V"]), app("edit.undo", &["Cmd+Z"])];
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), app("edit.cut", &["Cmd+X"]), app("edit.paste", &["Cmd+V"]), app("edit.undo", &["Cmd+Z"])];
|
||||
let mut o = Overrides::new();
|
||||
o.insert("edit.copy", vec!["Nowhere: Cmd+K".into()]);
|
||||
o.insert("edit.cut", vec!["Alt+F4".into()]);
|
||||
o.insert("edit.paste", vec!["Nowhere: Cmd+K".into(), "Alt+F4".into(), "Cmd+J".into()]);
|
||||
o.insert("edit.undo", vec![]);
|
||||
let km = Keymap::new(catalogue, Scopes::new(), Platform::Linux).with_reserved(reserved::system(Platform::Linux)).with_overrides(o);
|
||||
let km = Keymap::new(catalog, Scopes::new(), Platform::Linux).with_reserved(reserved::system(Platform::Linux)).with_overrides(o);
|
||||
assert_eq!(km.bindings(&cmd("edit.copy")), vec![b("Cmd+C")], "only an unknown scope");
|
||||
assert_eq!(km.bindings(&cmd("edit.cut")), vec![b("Cmd+X")], "only a reserved chord");
|
||||
assert_eq!(km.bindings(&cmd("edit.paste")), vec![b("Cmd+J")]);
|
||||
@@ -450,8 +450,8 @@ fn entries_with_nothing_usable_keep_the_defaults() {
|
||||
|
||||
#[test]
|
||||
fn overrides_hold_only_what_differs_from_the_defaults() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), in_scope("timeline.mark", "Timeline", &["M"])];
|
||||
let mut km = Keymap::new(catalogue, scopes(), Platform::Mac);
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), in_scope("timeline.mark", "Timeline", &["M"])];
|
||||
let mut km = Keymap::new(catalog, scopes(), Platform::Mac);
|
||||
let copy = cmd("edit.copy");
|
||||
km.assign(©, b("Timeline: Cmd+K"), Policy::Steal).unwrap();
|
||||
assert_eq!(km.overrides().get("edit.copy"), Some(&["Cmd+C".to_string(), "Timeline: Cmd+K".to_string()][..]));
|
||||
@@ -468,10 +468,10 @@ fn overrides_hold_only_what_differs_from_the_defaults() {
|
||||
|
||||
#[test]
|
||||
fn edits_keep_the_texts_this_build_cannot_use() {
|
||||
let catalogue = vec![app("file.quit", &["Cmd+Q"]), app("file.close", &["Cmd+W"]), app("file.save", &["Cmd+S"])];
|
||||
let catalog = vec![app("file.quit", &["Cmd+Q"]), app("file.close", &["Cmd+W"]), app("file.save", &["Cmd+S"])];
|
||||
let mut o = Overrides::new();
|
||||
o.insert("file.quit", vec!["Cmd+Q".into(), "Cmd+FutureKey".into(), "Nowhere: Cmd+J".into()]);
|
||||
let mut km = Keymap::new(catalogue.clone(), scopes(), Platform::Mac).with_overrides(o);
|
||||
let mut km = Keymap::new(catalog.clone(), scopes(), Platform::Mac).with_overrides(o);
|
||||
let quit = cmd("file.quit");
|
||||
let texts = |km: &Keymap| km.overrides().get("file.quit").map(<[String]>::to_vec);
|
||||
let strings = |t: &[&str]| Some(t.iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
@@ -480,7 +480,7 @@ fn edits_keep_the_texts_this_build_cannot_use() {
|
||||
assert_eq!(texts(&km), strings(&["Cmd+Q", "Cmd+W", "Cmd+FutureKey", "Nowhere: Cmd+J"]));
|
||||
km.assign(&cmd("file.save"), b("Cmd+Q"), Policy::Steal).unwrap();
|
||||
assert_eq!(texts(&km), strings(&["Cmd+W", "Cmd+FutureKey", "Nowhere: Cmd+J"]), "taken by a Steal");
|
||||
let reloaded = Keymap::new(catalogue, scopes(), Platform::Mac).with_overrides(km.overrides().clone());
|
||||
let reloaded = Keymap::new(catalog, scopes(), Platform::Mac).with_overrides(km.overrides().clone());
|
||||
assert_eq!(reloaded.bindings(&quit), vec![b("Cmd+W")]);
|
||||
|
||||
// An entry of only such texts would load as the defaults, so a cleared command drops them.
|
||||
@@ -500,17 +500,17 @@ fn an_override_equal_to_the_defaults_on_this_platform_is_not_stored() {
|
||||
|
||||
#[test]
|
||||
fn an_empty_text_in_the_map_form_removes_the_shortcut() {
|
||||
let catalogue = vec![app("edit.copy", &["Cmd+C"]), app("edit.paste", &["Cmd+V"]), app("edit.cut", &["Cmd+X"])];
|
||||
let catalog = vec![app("edit.copy", &["Cmd+C"]), app("edit.paste", &["Cmd+V"]), app("edit.cut", &["Cmd+X"])];
|
||||
let o: Overrides = serde_json::from_str(r#"{"edit.copy": "", "edit.paste": [""], "edit.cut": "Cmd+K"}"#).unwrap();
|
||||
assert!(o.load_notes().is_empty(), "{:?}", o.load_notes());
|
||||
let km = Keymap::new(catalogue.clone(), Scopes::new(), Platform::Mac).with_overrides(o);
|
||||
let km = Keymap::new(catalog.clone(), Scopes::new(), Platform::Mac).with_overrides(o);
|
||||
assert!(km.bindings(&cmd("edit.copy")).is_empty(), "a removed default stays removed");
|
||||
assert!(km.bindings(&cmd("edit.paste")).is_empty());
|
||||
assert_eq!(km.bindings(&cmd("edit.cut")), vec![b("Cmd+K")]);
|
||||
assert!(km.problems().is_empty());
|
||||
|
||||
let saved = serde_json::to_string(km.overrides()).unwrap();
|
||||
let again = Keymap::new(catalogue, Scopes::new(), Platform::Mac).with_overrides(serde_json::from_str(&saved).unwrap());
|
||||
let again = Keymap::new(catalog, Scopes::new(), Platform::Mac).with_overrides(serde_json::from_str(&saved).unwrap());
|
||||
assert!(again.bindings(&cmd("edit.copy")).is_empty(), "{saved}");
|
||||
}
|
||||
|
||||
@@ -548,9 +548,9 @@ fn overrides_write_the_map_form_unless_a_command_has_several_keys() {
|
||||
|
||||
#[test]
|
||||
fn ctrl_folds_into_cmd_off_macos() {
|
||||
let catalogue = vec![app("edit.cut", &["Cmd+X"]), app("mac.cut", &["Ctrl+X"])];
|
||||
let win = Keymap::new(catalogue.clone(), Scopes::new(), Platform::Windows);
|
||||
let mac = Keymap::new(catalogue, Scopes::new(), Platform::Mac);
|
||||
let catalog = vec![app("edit.cut", &["Cmd+X"]), app("mac.cut", &["Ctrl+X"])];
|
||||
let win = Keymap::new(catalog.clone(), Scopes::new(), Platform::Windows);
|
||||
let mac = Keymap::new(catalog, Scopes::new(), Platform::Mac);
|
||||
let conflicts = win.conflicts();
|
||||
assert_eq!(conflicts.len(), 1);
|
||||
assert_eq!((conflicts[0].kind, conflicts[0].chord.to_string()), (ConflictKind::Clash, "Cmd+X".to_string()));
|
||||
@@ -606,12 +606,12 @@ fn hostile_overrides_load_without_panic_and_report_problems() {
|
||||
#[test]
|
||||
fn reset_removes_hand_written_variants_of_a_key() {
|
||||
let layers = CommandRef::with_params("window.panel", json!({"panel": "layers"}));
|
||||
let catalogue = vec![Bindable::new(layers.clone(), "Layers", Scope::app()).with_default(b("F7"))];
|
||||
let catalog = vec![Bindable::new(layers.clone(), "Layers", Scope::app()).with_default(b("F7"))];
|
||||
let load = || {
|
||||
let mut o = Overrides::new();
|
||||
o.insert(r#"window.panel {"panel":"layers"}"#, vec!["Cmd+L".into()]);
|
||||
o.insert(r#"window.panel { "panel": "layers" }"#, vec!["Cmd+K".into()]);
|
||||
Keymap::new(catalogue.clone(), Scopes::new(), Platform::Mac).with_overrides(o)
|
||||
Keymap::new(catalog.clone(), Scopes::new(), Platform::Mac).with_overrides(o)
|
||||
};
|
||||
let mut km = load();
|
||||
assert_eq!(km.bindings(&layers), vec![b("Cmd+L")], "the canonical entry wins");
|
||||
@@ -622,6 +622,6 @@ fn reset_removes_hand_written_variants_of_a_key() {
|
||||
let mut km = load();
|
||||
km.set_bindings(&layers, vec![b("F7")], Policy::Steal).unwrap();
|
||||
assert!(km.overrides().is_empty(), "{:?}", km.overrides());
|
||||
let again = Keymap::new(catalogue.clone(), Scopes::new(), Platform::Mac).with_overrides(km.overrides().clone());
|
||||
let again = Keymap::new(catalog.clone(), Scopes::new(), Platform::Mac).with_overrides(km.overrides().clone());
|
||||
assert_eq!(again.bindings(&layers), vec![b("F7")]);
|
||||
}
|
||||
@@ -34,13 +34,13 @@ fn open(dir: &std::path::Path) -> SettingsStore<Prefs> {
|
||||
}
|
||||
|
||||
fn keymap(overrides: Overrides) -> Keymap {
|
||||
let catalogue = vec![
|
||||
let catalog = vec![
|
||||
Bindable::new(CommandRef::with_params("window.panel", json!({"panel": "layers"})), "Layers", Scope::app()),
|
||||
Bindable::new("text.bold", "Bold", Scope::app()),
|
||||
Bindable::new("edit.copy", "Copy", Scope::app()).with_default(Binding::parse("Cmd+C").unwrap()),
|
||||
Bindable::new("edit.paste", "Paste", Scope::app()).with_default(Binding::parse("Cmd+V").unwrap()),
|
||||
];
|
||||
Keymap::new(catalogue, Scopes::new().with_context("Text"), Platform::Mac).with_overrides(overrides)
|
||||
Keymap::new(catalog, Scopes::new().with_context("Text"), Platform::Mac).with_overrides(overrides)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user