Buckets:
| import { Component, For, Show, createMemo, lazy, onCleanup, onMount } from "solid-js" | |
| import { createStore } from "solid-js/store" | |
| import { makeEventListener } from "@solid-primitives/event-listener" | |
| import { Button } from "@opencode-ai/ui/button" | |
| import { Icon } from "@opencode-ai/ui/icon" | |
| import { IconButton } from "@opencode-ai/ui/icon-button" | |
| import { TextField } from "@opencode-ai/ui/text-field" | |
| import { showToast } from "@/utils/toast" | |
| import fuzzysort from "fuzzysort" | |
| import { formatKeybind, parseKeybind, useCommand } from "@/context/command" | |
| import { useLanguage } from "@/context/language" | |
| import { useSettings } from "@/context/settings" | |
| import { SettingsList } from "./settings-list" | |
| const ButtonV2 = lazy(() => import("@opencode-ai/ui/v2/button-v2").then((module) => ({ default: module.ButtonV2 }))) | |
| const IconV2 = lazy(() => import("@opencode-ai/ui/v2/icon").then((module) => ({ default: module.Icon }))) | |
| const IconButtonV2 = lazy(() => | |
| import("@opencode-ai/ui/v2/icon-button-v2").then((module) => ({ default: module.IconButtonV2 })), | |
| ) | |
| const TextInputV2 = lazy(() => | |
| import("@opencode-ai/ui/v2/text-input-v2").then((module) => ({ default: module.TextInputV2 })), | |
| ) | |
| const SettingsListV2 = lazy(() => | |
| import("./settings-v2/parts/list").then((module) => ({ default: module.SettingsListV2 })), | |
| ) | |
| const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) | |
| const PALETTE_ID = "command.palette" | |
| const DEFAULT_PALETTE_KEYBIND = "mod+shift+p" | |
| type KeybindGroup = "General" | "Session" | "Navigation" | "Model and agent" | "Terminal" | "Prompt" | |
| type KeybindMeta = { | |
| title: string | |
| group: KeybindGroup | |
| } | |
| type KeybindMap = Record<string, string | undefined> | |
| type CommandContext = ReturnType<typeof useCommand> | |
| const GROUPS: KeybindGroup[] = ["General", "Session", "Navigation", "Model and agent", "Terminal", "Prompt"] | |
| type GroupKey = | |
| | "settings.shortcuts.group.general" | |
| | "settings.shortcuts.group.session" | |
| | "settings.shortcuts.group.navigation" | |
| | "settings.shortcuts.group.modelAndAgent" | |
| | "settings.shortcuts.group.terminal" | |
| | "settings.shortcuts.group.prompt" | |
| const groupKey: Record<KeybindGroup, GroupKey> = { | |
| General: "settings.shortcuts.group.general", | |
| Session: "settings.shortcuts.group.session", | |
| Navigation: "settings.shortcuts.group.navigation", | |
| "Model and agent": "settings.shortcuts.group.modelAndAgent", | |
| Terminal: "settings.shortcuts.group.terminal", | |
| Prompt: "settings.shortcuts.group.prompt", | |
| } | |
| function groupFor(id: string): KeybindGroup { | |
| if (id === PALETTE_ID) return "General" | |
| if (id.startsWith("terminal.")) return "Terminal" | |
| if (id.startsWith("model.") || id.startsWith("agent.") || id.startsWith("mcp.")) return "Model and agent" | |
| if (id.startsWith("file.") || id.startsWith("fileTree.")) return "Navigation" | |
| if (id.startsWith("prompt.")) return "Prompt" | |
| if ( | |
| id.startsWith("session.") || | |
| id.startsWith("message.") || | |
| id.startsWith("permissions.") || | |
| id.startsWith("steps.") || | |
| id.startsWith("review.") | |
| ) | |
| return "Session" | |
| return "General" | |
| } | |
| function isModifier(key: string) { | |
| return key === "Shift" || key === "Control" || key === "Alt" || key === "Meta" | |
| } | |
| function normalizeKey(key: string) { | |
| if (key === ",") return "comma" | |
| if (key === "+") return "plus" | |
| if (key === " ") return "space" | |
| return key.toLowerCase() | |
| } | |
| function recordKeybind(event: KeyboardEvent) { | |
| if (isModifier(event.key)) return | |
| const parts: string[] = [] | |
| const mod = IS_MAC ? event.metaKey : event.ctrlKey | |
| if (mod) parts.push("mod") | |
| if (IS_MAC && event.ctrlKey) parts.push("ctrl") | |
| if (!IS_MAC && event.metaKey) parts.push("meta") | |
| if (event.altKey) parts.push("alt") | |
| if (event.shiftKey) parts.push("shift") | |
| const key = normalizeKey(event.key) | |
| if (!key) return | |
| parts.push(key) | |
| return parts.join("+") | |
| } | |
| function signatures(config: string | undefined) { | |
| if (!config) return [] | |
| const sigs: string[] = [] | |
| for (const kb of parseKeybind(config)) { | |
| const parts: string[] = [] | |
| if (kb.ctrl) parts.push("ctrl") | |
| if (kb.alt) parts.push("alt") | |
| if (kb.shift) parts.push("shift") | |
| if (kb.meta) parts.push("meta") | |
| if (kb.key) parts.push(kb.key) | |
| if (parts.length === 0) continue | |
| sigs.push(parts.join("+")) | |
| } | |
| return sigs | |
| } | |
| function keybinds(value: unknown): KeybindMap { | |
| if (!value || typeof value !== "object" || Array.isArray(value)) return {} | |
| return value as KeybindMap | |
| } | |
| function listFor(command: CommandContext, map: KeybindMap, palette: string) { | |
| const out = new Map<string, KeybindMeta>() | |
| out.set(PALETTE_ID, { title: palette, group: "General" }) | |
| for (const opt of command.catalog) { | |
| if (opt.id.startsWith("suggested.")) continue | |
| if (opt.hidden) continue | |
| out.set(opt.id, { title: opt.title, group: groupFor(opt.id) }) | |
| } | |
| for (const opt of command.options) { | |
| if (opt.id.startsWith("suggested.")) continue | |
| if (opt.hidden) continue | |
| out.set(opt.id, { title: opt.title, group: groupFor(opt.id) }) | |
| } | |
| for (const [id, value] of Object.entries(map)) { | |
| if (typeof value !== "string") continue | |
| if (out.has(id)) continue | |
| out.set(id, { title: id, group: groupFor(id) }) | |
| } | |
| return out | |
| } | |
| function groupedFor(list: Map<string, KeybindMeta>) { | |
| const out = new Map<KeybindGroup, string[]>() | |
| for (const group of GROUPS) out.set(group, []) | |
| for (const [id, item] of list) { | |
| const ids = out.get(item.group) | |
| if (!ids) continue | |
| ids.push(id) | |
| } | |
| for (const group of GROUPS) { | |
| const ids = out.get(group) | |
| if (!ids) continue | |
| ids.sort((a, b) => (list.get(a)?.title ?? "").localeCompare(list.get(b)?.title ?? "")) | |
| } | |
| return out | |
| } | |
| function filteredFor( | |
| query: string, | |
| list: Map<string, KeybindMeta>, | |
| grouped: Map<KeybindGroup, string[]>, | |
| keybind: (id: string) => string, | |
| ) { | |
| const value = query.toLowerCase().trim() | |
| if (!value) return grouped | |
| const out = new Map<KeybindGroup, string[]>() | |
| for (const group of GROUPS) out.set(group, []) | |
| const items = Array.from(list.entries()).map(([id, meta]) => ({ | |
| id, | |
| title: meta.title, | |
| group: meta.group, | |
| keybind: keybind(id), | |
| })) | |
| const results = fuzzysort.go(value, items, { | |
| keys: ["title", "keybind"], | |
| threshold: -10000, | |
| }) | |
| for (const result of results) { | |
| const ids = out.get(result.obj.group) | |
| if (!ids) continue | |
| ids.push(result.obj.id) | |
| } | |
| return out | |
| } | |
| function useKeyCapture(input: { | |
| active: () => string | null | |
| stop: () => void | |
| set: (id: string, keybind: string) => void | |
| used: () => Map<string, { id: string; title: string }[]> | |
| language: ReturnType<typeof useLanguage> | |
| }) { | |
| onMount(() => { | |
| const handle = (event: KeyboardEvent) => { | |
| const id = input.active() | |
| if (!id) return | |
| event.preventDefault() | |
| event.stopPropagation() | |
| event.stopImmediatePropagation() | |
| if (event.key === "Escape") { | |
| input.stop() | |
| return | |
| } | |
| const clear = | |
| (event.key === "Backspace" || event.key === "Delete") && | |
| !event.ctrlKey && | |
| !event.metaKey && | |
| !event.altKey && | |
| !event.shiftKey | |
| if (clear) { | |
| input.set(id, "none") | |
| input.stop() | |
| return | |
| } | |
| const next = recordKeybind(event) | |
| if (!next) return | |
| const conflicts = new Map<string, string>() | |
| for (const sig of signatures(next)) { | |
| for (const item of input.used().get(sig) ?? []) { | |
| if (item.id === id) continue | |
| conflicts.set(item.id, item.title) | |
| } | |
| } | |
| if (conflicts.size > 0) { | |
| showToast({ | |
| title: input.language.t("settings.shortcuts.conflict.title"), | |
| description: input.language.t("settings.shortcuts.conflict.description", { | |
| keybind: formatKeybind(next, input.language.t), | |
| titles: [...conflicts.values()].join(", "), | |
| }), | |
| }) | |
| return | |
| } | |
| input.set(id, next) | |
| input.stop() | |
| } | |
| makeEventListener(document, "keydown", handle, { capture: true }) | |
| }) | |
| } | |
| export const SettingsKeybinds: Component<{ v2?: boolean }> = (props) => { | |
| const command = useCommand() | |
| const language = useLanguage() | |
| const settings = useSettings() | |
| const [store, setStore] = createStore({ | |
| active: null as string | null, | |
| filter: "", | |
| }) | |
| const stop = () => { | |
| if (!store.active) return | |
| setStore("active", null) | |
| command.keybinds(true) | |
| } | |
| const start = (id: string) => { | |
| if (store.active === id) { | |
| stop() | |
| return | |
| } | |
| if (store.active) stop() | |
| setStore("active", id) | |
| command.keybinds(false) | |
| } | |
| const map = createMemo(() => keybinds(settings.current.keybinds)) | |
| const hasOverrides = createMemo(() => Object.values(map()).some((x) => typeof x === "string")) | |
| const resetAll = () => { | |
| stop() | |
| settings.keybinds.resetAll() | |
| showToast({ | |
| title: language.t("settings.shortcuts.reset.toast.title"), | |
| description: language.t("settings.shortcuts.reset.toast.description"), | |
| }) | |
| } | |
| const list = createMemo(() => { | |
| language.locale() | |
| return listFor(command, map(), language.t("command.palette")) | |
| }) | |
| const title = (id: string) => list().get(id)?.title ?? "" | |
| const grouped = createMemo(() => groupedFor(list())) | |
| const filtered = createMemo(() => { | |
| return filteredFor(store.filter, list(), grouped(), (id) => command.keybind(id) || "") | |
| }) | |
| const hasResults = createMemo(() => { | |
| for (const group of GROUPS) { | |
| const ids = filtered().get(group) ?? [] | |
| if (ids.length > 0) return true | |
| } | |
| return false | |
| }) | |
| const used = createMemo(() => { | |
| const map = new Map<string, { id: string; title: string }[]>() | |
| const add = (key: string, value: { id: string; title: string }) => { | |
| const list = map.get(key) | |
| if (!list) { | |
| map.set(key, [value]) | |
| return | |
| } | |
| list.push(value) | |
| } | |
| const palette = settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND | |
| for (const sig of signatures(palette)) { | |
| add(sig, { id: PALETTE_ID, title: title(PALETTE_ID) }) | |
| } | |
| const valueFor = (id: string) => { | |
| const custom = settings.keybinds.get(id) | |
| if (typeof custom === "string") return custom | |
| const live = command.options.find((x) => x.id === id) | |
| if (live?.keybind) return live.keybind | |
| const meta = command.catalog.find((x) => x.id === id) | |
| return meta?.keybind | |
| } | |
| for (const id of list().keys()) { | |
| if (id === PALETTE_ID) continue | |
| for (const sig of signatures(valueFor(id))) { | |
| add(sig, { id, title: title(id) }) | |
| } | |
| } | |
| return map | |
| }) | |
| const setKeybind = (id: string, keybind: string) => settings.keybinds.set(id, keybind) | |
| useKeyCapture({ | |
| active: () => store.active, | |
| stop, | |
| set: setKeybind, | |
| used, | |
| language, | |
| }) | |
| onCleanup(() => { | |
| if (store.active) command.keybinds(true) | |
| }) | |
| const emptyResults = ( | |
| <Show when={store.filter && !hasResults()}> | |
| <div | |
| classList={{ | |
| "flex flex-col items-center justify-center py-12 text-center": !props.v2, | |
| "settings-v2-shortcuts-status": props.v2, | |
| }} | |
| > | |
| <span | |
| classList={{ | |
| "text-14-regular text-text-weak": !props.v2, | |
| }} | |
| > | |
| {language.t("settings.shortcuts.search.empty")} | |
| </span> | |
| <Show when={store.filter}> | |
| <span | |
| classList={{ | |
| "text-14-regular text-text-strong mt-1": !props.v2, | |
| "settings-v2-shortcuts-status-filter": props.v2, | |
| }} | |
| > | |
| "{store.filter}" | |
| </span> | |
| </Show> | |
| </div> | |
| </Show> | |
| ) | |
| const List = props.v2 ? SettingsListV2 : SettingsList | |
| const groups = ( | |
| <div | |
| classList={{ | |
| "settings-v2-shortcuts flex flex-col gap-8": props.v2, | |
| "flex flex-col gap-8 max-w-[720px]": !props.v2, | |
| }} | |
| > | |
| <For each={GROUPS}> | |
| {(group) => ( | |
| <Show when={(filtered().get(group) ?? []).length > 0}> | |
| <div | |
| classList={{ | |
| "settings-v2-section": props.v2, | |
| "flex flex-col gap-1": !props.v2, | |
| }} | |
| > | |
| <h3 | |
| classList={{ | |
| "settings-v2-section-title": props.v2, | |
| "text-14-medium text-text-strong pb-2": !props.v2, | |
| }} | |
| > | |
| {language.t(groupKey[group])} | |
| </h3> | |
| <List> | |
| <For each={filtered().get(group) ?? []}> | |
| {(id) => ( | |
| <div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none"> | |
| <span | |
| classList={{ | |
| "text-14-regular text-text-strong": !props.v2, | |
| }} | |
| > | |
| {title(id)} | |
| </span> | |
| <button | |
| type="button" | |
| data-keybind-id={id} | |
| classList={{ | |
| "settings-v2-keybind-button": props.v2, | |
| "settings-v2-keybind-button--active": props.v2 && store.active === id, | |
| "h-8 px-3 rounded-md text-12-regular": !props.v2, | |
| "bg-surface-base text-text-subtle hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active": | |
| !props.v2 && store.active !== id, | |
| "border border-border-weak-base bg-surface-inset-base text-text-weak": | |
| !props.v2 && store.active === id, | |
| }} | |
| onClick={() => start(id)} | |
| > | |
| <Show | |
| when={store.active === id} | |
| fallback={command.keybind(id) || language.t("settings.shortcuts.unassigned")} | |
| > | |
| {language.t("settings.shortcuts.pressKeys")} | |
| </Show> | |
| </button> | |
| </div> | |
| )} | |
| </For> | |
| </List> | |
| </div> | |
| </Show> | |
| )} | |
| </For> | |
| {emptyResults} | |
| </div> | |
| ) | |
| return ( | |
| <Show | |
| when={props.v2} | |
| fallback={ | |
| <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"> | |
| <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]"> | |
| <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]"> | |
| <div class="flex items-center justify-between gap-4"> | |
| <h2 class="text-16-medium text-text-strong">{language.t("settings.shortcuts.title")}</h2> | |
| <Button size="small" variant="secondary" onClick={resetAll} disabled={!hasOverrides()}> | |
| {language.t("settings.shortcuts.reset.button")} | |
| </Button> | |
| </div> | |
| <div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base"> | |
| <Icon name="magnifying-glass" class="text-icon-weak-base flex-shrink-0" /> | |
| <TextField | |
| variant="ghost" | |
| type="text" | |
| value={store.filter} | |
| onChange={(v) => setStore("filter", v)} | |
| placeholder={language.t("settings.shortcuts.search.placeholder")} | |
| spellcheck={false} | |
| autocorrect="off" | |
| autocomplete="off" | |
| autocapitalize="off" | |
| class="flex-1" | |
| /> | |
| <Show when={store.filter}> | |
| <IconButton icon="circle-x" variant="ghost" onClick={() => setStore("filter", "")} /> | |
| </Show> | |
| </div> | |
| </div> | |
| </div> | |
| {groups} | |
| </div> | |
| } | |
| > | |
| <> | |
| <div class="settings-v2-tab-header settings-v2-tab-header--stacked"> | |
| <div class="settings-v2-tab-header-row"> | |
| <h2 class="settings-v2-tab-title">{language.t("settings.shortcuts.title")}</h2> | |
| <ButtonV2 variant="ghost" onClick={resetAll} disabled={!hasOverrides()}> | |
| {language.t("settings.shortcuts.reset.button")} | |
| </ButtonV2> | |
| </div> | |
| <div class="settings-v2-tab-search"> | |
| <TextInputV2 | |
| type="search" | |
| appearance="base" | |
| value={store.filter} | |
| onInput={(event) => setStore("filter", event.currentTarget.value)} | |
| placeholder={language.t("settings.shortcuts.search.placeholder")} | |
| spellcheck={false} | |
| autocorrect="off" | |
| autocomplete="off" | |
| autocapitalize="off" | |
| aria-label={language.t("settings.shortcuts.search.placeholder")} | |
| /> | |
| <Show when={store.filter}> | |
| <IconButtonV2 | |
| type="button" | |
| variant="ghost-muted" | |
| size="small" | |
| class="settings-v2-tab-search-clear" | |
| icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />} | |
| onClick={() => setStore("filter", "")} | |
| /> | |
| </Show> | |
| </div> | |
| </div> | |
| <div class="settings-v2-tab-body">{groups}</div> | |
| </> | |
| </Show> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 17.6 kB
- Xet hash:
- 85ad7b35270d6d7812698bc64516157b6b77acd5e8f7ee6a5a4216cb6bef6b71
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.