Shortcuts you invent, rules you write, gestures you own - all still just data
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -69,6 +69,12 @@ Singleton {
|
||||
// Settings that are real but have no schema entry, because the system owns
|
||||
// them rather than Panama. Without these, searching "timezone" would fail
|
||||
// on a settings app that plainly has one.
|
||||
//
|
||||
// An entry may name a `section` as well as a page. A page with tabs opens
|
||||
// on the tab holding the thing that was searched for, rather than on
|
||||
// whichever tab it opens on by default -- see SettingsSidebar. Sections are
|
||||
// only worth naming for a page that consumes one (ShellState.takeSettings-
|
||||
// Section); everything else leaves it off and routes as before.
|
||||
readonly property var extraEntries: [
|
||||
{ label: "Manual", detail: "How this desktop works, in chapters", page: "manual" },
|
||||
{ label: "Getting started", detail: "Coming from GNOME, macOS or Windows", page: "manual" },
|
||||
@@ -92,6 +98,22 @@ Singleton {
|
||||
{ label: "IP address", detail: "The address, gateway, DNS servers, and hardware address of a connection", page: "connectivity" },
|
||||
{ label: "Forget a Wi-Fi network", detail: "Remove a saved network so it stops connecting on its own", page: "connectivity" },
|
||||
{ label: "Enterprise Wi-Fi", detail: "Join a network that asks for an identity and a password", page: "connectivity" },
|
||||
// Tier 2 network truths. Each of these was a reason to open a terminal
|
||||
// or GNOME's panel, and none of them is the label of a preference: a
|
||||
// static address is a profile property, a saved network is a file
|
||||
// NetworkManager keeps, and metered is a flag on both.
|
||||
{ label: "Saved networks", detail: "Every network this machine remembers, including the ones nowhere near you, and forgetting one", page: "connectivity" },
|
||||
{ label: "Join a hidden network", detail: "A network that does not broadcast its name — type the name and its security", page: "connectivity" },
|
||||
{ label: "Hidden network", detail: "Join a Wi-Fi network that does not announce itself", page: "connectivity" },
|
||||
{ label: "Metered connection", detail: "Mark a connection as costing money by the byte, so updates and large downloads wait", page: "connectivity" },
|
||||
{ label: "Static IP address", detail: "Set a manual IPv4 or IPv6 address, gateway and DNS for one connection", page: "connectivity" },
|
||||
{ label: "Manual IP address", detail: "Turn off DHCP for a connection and enter the address yourself", page: "connectivity" },
|
||||
{ label: "DNS servers", detail: "The nameservers a connection uses, and replacing the ones it is handed", page: "connectivity" },
|
||||
{ label: "Show the Wi-Fi password", detail: "A QR code a phone can scan, so nobody has to read the password out", page: "connectivity" },
|
||||
// The two words people type for the same socket. Neither appears in the
|
||||
// page's own labels, which say "Wired" once and "Ethernet" once.
|
||||
{ label: "Wired network", detail: "The Ethernet connection, its link speed, and its addresses", page: "connectivity" },
|
||||
{ label: "Ethernet", detail: "The wired connection: turn it off, or open its addresses", page: "connectivity" },
|
||||
{ label: "Default applications", detail: "Browser, mail, files", page: "applications" },
|
||||
// The Applications tab manages applications now, rather than only
|
||||
// pointing file types at them, so the things people come looking for —
|
||||
@@ -264,6 +286,11 @@ Singleton {
|
||||
{ label: "Pinned applications", detail: "Reorder the dock by dragging, here or on the dock itself", page: "dock" },
|
||||
{ label: "Bar text", detail: "Keep the bar legible on any wallpaper", page: "bar" },
|
||||
{ label: "Control Center sections", detail: "Choose what the panel offers", page: "control-center" },
|
||||
{ label: "Do Not Disturb tile", detail: "The Control Center switch that holds banners back, beside Presentation", page: "control-center" },
|
||||
// Signing out has no preference anywhere: it is a power-menu verb, and
|
||||
// "log out" returned nothing at all on a desktop that plainly does it.
|
||||
{ label: "Log out", detail: "Sign out of this session from the power menu — the same menu that restarts and powers off", page: "power" },
|
||||
{ label: "Sign out", detail: "End this session and return to the login screen", page: "power" },
|
||||
{ label: "Do Not Disturb", detail: "Hold banners back until you turn it off", page: "notifications" },
|
||||
{ label: "Quiet hours", detail: "The schedule the Sleep focus mode keeps", page: "notifications" },
|
||||
{ label: "Critical alerts break through", detail: "Let urgent notifications past Do Not Disturb", page: "notifications" },
|
||||
@@ -277,6 +304,18 @@ Singleton {
|
||||
// a row — so searching for the thing people actually want to do would
|
||||
// otherwise find only the shortcut it is being done to.
|
||||
{ label: "Rebind a shortcut", detail: "Change the keys an action answers to, or put them back", page: "shortcuts" },
|
||||
// Tier 2. A custom shortcut, an app rule and a gesture assignment are
|
||||
// all the same thing wearing three hats -- a named action -- and none
|
||||
// of the three is a preference with a label to find it by.
|
||||
{ label: "Custom shortcut", detail: "Bind your own keys to an application, a shell action, or a window action", page: "shortcuts" },
|
||||
{ label: "Add a shortcut", detail: "Press the chord you want, then pick what it should do", page: "shortcuts" },
|
||||
{ label: "Launch an app with a shortcut", detail: "Give an application its own key combination", page: "shortcuts" },
|
||||
{ label: "App rules", detail: "Per-application window rules: float, centre, size, workspace, and no dimming", page: "tiling" },
|
||||
{ label: "Window rules", detail: "Make one application always float, open on a workspace, or skip the animations", page: "tiling" },
|
||||
{ label: "Always float a window", detail: "A per-application rule, so one application stops being tiled", page: "tiling" },
|
||||
{ label: "Gestures", detail: "Three-finger swipes as shipped, and four-finger swipes you assign yourself", page: "mouse" },
|
||||
{ label: "Touchpad gestures", detail: "What swiping with three or four fingers does", page: "mouse" },
|
||||
{ label: "Four-finger swipe", detail: "Assign an application or a shell action to each direction", page: "mouse" },
|
||||
{ label: "Pointer test area", detail: "Scribble and scroll to feel a pointer change before keeping it", page: "mouse" },
|
||||
{ label: "Connected input devices", detail: "The keyboards, mice, and touchpad this machine can see", page: "mouse" },
|
||||
// Accessibility. The schema covers the switches by their own labels, so
|
||||
@@ -294,13 +333,18 @@ Singleton {
|
||||
{ label: "Screen reader", detail: "Start Orca and see whether the accessibility bus is up", page: "accessibility" },
|
||||
{ label: "Orca", detail: "The screen reader: whether it is running, and starting or stopping it", page: "accessibility" },
|
||||
{ label: "Sticky keys", detail: "Why sticky, slow and bounce keys are not offered in this session", page: "accessibility" },
|
||||
// The colour filter is a compositor shader rather than a switch, so it
|
||||
// is findable by the condition rather than only by the word "filter".
|
||||
{ label: "Color filter", detail: "A whole-screen filter the compositor renders: grayscale, or one for each kind of colour blindness", page: "accessibility" },
|
||||
{ label: "Grayscale", detail: "Drain the colour out of the whole screen", page: "accessibility" },
|
||||
{ label: "Color blindness", detail: "Protanopia, deuteranopia and tritanopia filters applied to the whole screen", page: "accessibility" },
|
||||
{ label: "Dictation", detail: "Speech to text with Super+D, typed where the cursor is", page: "dictation" },
|
||||
{ label: "Speech to text", detail: "Set up the local speech server and model", page: "dictation" },
|
||||
{ label: "System Health", detail: "Check Panama services, integrations, tools, and recovery actions", page: "services" },
|
||||
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" },
|
||||
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid color", page: "appearance" },
|
||||
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" },
|
||||
{ label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" },
|
||||
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid color", page: "appearance", section: "background" },
|
||||
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance", section: "background" },
|
||||
{ label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance", section: "background" },
|
||||
{ label: "Arrange displays", detail: "Drag connected displays into their physical positions", page: "displays" },
|
||||
{ label: "Monitor position", detail: "Set where each display sits in the desktop", page: "displays" },
|
||||
{ label: "Primary display", detail: "Choose the display that anchors the desktop", page: "displays" },
|
||||
@@ -316,18 +360,18 @@ Singleton {
|
||||
{ label: "Mirror displays", detail: "Show the same picture on a second display", page: "displays" },
|
||||
{ label: "Variable refresh rate", detail: "Override the gaming policy for one display", page: "displays" },
|
||||
{ label: "Monitor brightness", detail: "The monitor's own backlight, over DDC", page: "displays" },
|
||||
{ label: "Theme profiles", detail: "Switch between Moon, Moon Rose, Day, and saved themes", page: "appearance" },
|
||||
{ label: "Themes", detail: "Light and dark mode, and the theme for each", page: "appearance" },
|
||||
{ label: "Dark mode", detail: "Flip the desktop to your dark theme", page: "appearance" },
|
||||
{ label: "Light mode", detail: "Flip the desktop to your light theme", page: "appearance" },
|
||||
{ label: "Theme editor", detail: "Build your own theme — colors, saturation, and effects", page: "appearance" },
|
||||
{ label: "Catppuccin", detail: "Mocha and Latte, in the theme galleries", page: "appearance" },
|
||||
{ label: "Nord", detail: "The arctic dark theme, in the gallery", page: "appearance" },
|
||||
{ label: "Gruvbox", detail: "Dark and light, in the theme galleries", page: "appearance" },
|
||||
{ label: "Everforest", detail: "Dark and light, in the theme galleries", page: "appearance" },
|
||||
{ label: "Tokyo Night", detail: "Moon and Day, the shipped defaults", page: "appearance" },
|
||||
{ label: "Video wallpaper", detail: "A looping video as the desktop background", page: "appearance" },
|
||||
{ label: "Advanced accent", detail: "Adjust primary and secondary hue, saturation, and value", page: "appearance" },
|
||||
{ label: "Theme profiles", detail: "Switch between Moon, Moon Rose, Day, and saved themes", page: "appearance", section: "themes" },
|
||||
{ label: "Themes", detail: "Light and dark mode, and the theme for each", page: "appearance", section: "themes" },
|
||||
{ label: "Dark mode", detail: "Flip the desktop to your dark theme", page: "appearance", section: "themes" },
|
||||
{ label: "Light mode", detail: "Flip the desktop to your light theme", page: "appearance", section: "themes" },
|
||||
{ label: "Theme editor", detail: "Build your own theme — colors, saturation, and effects", page: "appearance", section: "editor" },
|
||||
{ label: "Catppuccin", detail: "Mocha and Latte, in the theme galleries", page: "appearance", section: "themes" },
|
||||
{ label: "Nord", detail: "The arctic dark theme, in the gallery", page: "appearance", section: "themes" },
|
||||
{ label: "Gruvbox", detail: "Dark and light, in the theme galleries", page: "appearance", section: "themes" },
|
||||
{ label: "Everforest", detail: "Dark and light, in the theme galleries", page: "appearance", section: "themes" },
|
||||
{ label: "Tokyo Night", detail: "Moon and Day, the shipped defaults", page: "appearance", section: "themes" },
|
||||
{ label: "Video wallpaper", detail: "A looping video as the desktop background", page: "appearance", section: "background" },
|
||||
{ label: "Advanced accent", detail: "Adjust primary and secondary hue, saturation, and value", page: "appearance", section: "editor" },
|
||||
{ label: "Pick colour from screen", detail: "Sample an accent colour with hyprpicker", page: "appearance" }
|
||||
]
|
||||
|
||||
@@ -335,41 +379,77 @@ Singleton {
|
||||
return root.groupPages[group] ?? "home";
|
||||
}
|
||||
|
||||
// [{ label, detail, page, kind }] for a query. Empty query yields nothing:
|
||||
// the sidebar shows its normal navigation in that case.
|
||||
// Both sides of a comparison, in the one spelling.
|
||||
//
|
||||
// The hyphens go because they are a typographic choice rather than a word
|
||||
// boundary, and the desktop's most-searched noun is the worst case:
|
||||
// everything here spells it "Wi-Fi" and nobody types it that way, so "wifi"
|
||||
// matched nothing at all. Applied to the query and the index alike, so the
|
||||
// rule is a spelling equivalence rather than a special case for one word.
|
||||
function flatten(text: string): string {
|
||||
return String(text).trim().toLowerCase().replace(/-/g, "");
|
||||
}
|
||||
|
||||
// [{ label, detail, page, kind, section }] for a query. Empty query yields
|
||||
// nothing: the sidebar shows its normal navigation in that case.
|
||||
//
|
||||
// The needle is split on whitespace and every token has to appear somewhere
|
||||
// in the haystack -- an AND over words rather than one contiguous
|
||||
// substring. The old shape matched the whole query as typed, so "wifi
|
||||
// password", "log out" and "metered" returned nothing on a settings app
|
||||
// that has all three: the words are all present, just not adjacent and not
|
||||
// in that order. Order was the accidental part, and it was doing the most
|
||||
// damage.
|
||||
function search(query: string): var {
|
||||
const needle = String(query).trim().toLowerCase();
|
||||
const needle = root.flatten(query);
|
||||
if (needle === "")
|
||||
return [];
|
||||
|
||||
const tokens = needle.split(/\s+/).filter(token => token !== "");
|
||||
if (tokens.length === 0)
|
||||
return [];
|
||||
|
||||
function hit(haystack) {
|
||||
const text = root.flatten(haystack);
|
||||
return tokens.every(token => text.indexOf(token) >= 0);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
const seen = {};
|
||||
|
||||
function add(label, detail, page, kind) {
|
||||
function add(label, detail, page, kind, section) {
|
||||
const dedupe = `${kind}:${label}:${page}`;
|
||||
if (seen[dedupe])
|
||||
return;
|
||||
seen[dedupe] = true;
|
||||
results.push({ label: label, detail: detail, page: page, kind: kind });
|
||||
results.push({
|
||||
label: label,
|
||||
detail: detail,
|
||||
page: page,
|
||||
kind: kind,
|
||||
// "" means "the page's own first tab", which is every result
|
||||
// that does not name one. See SettingsSidebar.
|
||||
section: String(section ?? "")
|
||||
});
|
||||
}
|
||||
|
||||
for (const entry of PreferenceSchema.entries) {
|
||||
if (entry.internal)
|
||||
continue;
|
||||
const optionLabels = (entry.options ?? []).map(option => option.label).join(" ");
|
||||
const haystack = `${entry.label} ${entry.detail ?? ""} ${entry.group} ${optionLabels}`.toLowerCase();
|
||||
if (haystack.indexOf(needle) >= 0)
|
||||
add(entry.label, entry.detail ?? "", root.pageFor(entry.group), "setting");
|
||||
const haystack = `${entry.label} ${entry.detail ?? ""} ${entry.group} ${optionLabels}`;
|
||||
if (hit(haystack))
|
||||
add(entry.label, entry.detail ?? "", root.pageFor(entry.group), "setting", "");
|
||||
}
|
||||
|
||||
for (const entry of root.extraEntries) {
|
||||
if (`${entry.label} ${entry.detail}`.toLowerCase().indexOf(needle) >= 0)
|
||||
add(entry.label, entry.detail, entry.page, "setting");
|
||||
if (hit(`${entry.label} ${entry.detail}`))
|
||||
add(entry.label, entry.detail, entry.page, "setting", entry.section ?? "");
|
||||
}
|
||||
|
||||
for (const bind of Keybinds.binds) {
|
||||
if (bind.description.toLowerCase().indexOf(needle) >= 0)
|
||||
add(bind.description, bind.chord, "shortcuts", "shortcut");
|
||||
if (hit(bind.description))
|
||||
add(bind.description, bind.chord, "shortcuts", "shortcut", "");
|
||||
}
|
||||
|
||||
// Exact prefix matches first: typing "blur" should put "Blur" above
|
||||
@@ -377,15 +457,25 @@ Singleton {
|
||||
// its explanation. An exact enum option also leads: "slideshow" is a
|
||||
// mode choice, so Wallpaper mode belongs above the interval row that
|
||||
// merely explains it.
|
||||
//
|
||||
// Between those two comes the tokenized rule: a row whose LABEL holds
|
||||
// every word of the query beats one that only holds some of them, or
|
||||
// holds them in its explanation. Without it, "wifi password" would rank
|
||||
// every row whose detail happens to say "password" alongside the rows
|
||||
// that are actually about the Wi-Fi password.
|
||||
return results.sort((a, b) => {
|
||||
const aSpec = PreferenceSchema.entries.find(entry => entry.label === a.label);
|
||||
const bSpec = PreferenceSchema.entries.find(entry => entry.label === b.label);
|
||||
const ao = (aSpec?.options ?? []).some(option => option.label.toLowerCase() === needle);
|
||||
const bo = (bSpec?.options ?? []).some(option => option.label.toLowerCase() === needle);
|
||||
const ao = (aSpec?.options ?? []).some(option => root.flatten(option.label) === needle);
|
||||
const bo = (bSpec?.options ?? []).some(option => root.flatten(option.label) === needle);
|
||||
if (ao !== bo)
|
||||
return ao ? -1 : 1;
|
||||
const al = a.label.toLowerCase();
|
||||
const bl = b.label.toLowerCase();
|
||||
const al = root.flatten(a.label);
|
||||
const bl = root.flatten(b.label);
|
||||
const at = tokens.every(token => al.indexOf(token) >= 0) ? 0 : 1;
|
||||
const bt = tokens.every(token => bl.indexOf(token) >= 0) ? 0 : 1;
|
||||
if (at !== bt)
|
||||
return at - bt;
|
||||
const ap = al === needle ? 0 : (al.indexOf(needle) === 0 ? 1 : 2);
|
||||
const bp = bl === needle ? 0 : (bl.indexOf(needle) === 0 ? 1 : 2);
|
||||
return ap !== bp ? ap - bp : al.localeCompare(bl);
|
||||
|
||||
Reference in New Issue
Block a user