fix(desktop): color grouping

This commit is contained in:
Adam
2025-11-10 11:46:53 -06:00
parent a20489584e
commit 4f604b3839
2 changed files with 101 additions and 31 deletions

View File

@@ -1,6 +1,6 @@
import { createStore, produce } from "solid-js/store" import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "./helper" import { createSimpleContext } from "./helper"
import { batch, createEffect, createMemo } from "solid-js" import { batch, createEffect, createMemo, createSignal, on } from "solid-js"
import { useSync } from "./sync" import { useSync } from "./sync"
import { makePersisted } from "@solid-primitives/storage" import { makePersisted } from "@solid-primitives/storage"
import { TextSelection, useLocal } from "./local" import { TextSelection, useLocal } from "./local"
@@ -13,26 +13,74 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
const sync = useSync() const sync = useSync()
const local = useLocal() const local = useLocal()
const [store, setStore] = makePersisted( const seed = props.sessionId ?? "new-session"
const [persist, setPersist] = makePersisted(
createStore<{ createStore<{
prompt: Prompt
cursorPosition?: number
messageId?: string messageId?: string
tabs: { tabs: {
active?: string active?: string
opened: string[] opened: string[]
} }
}>({ }>({
prompt: [{ type: "text", content: "", start: 0, end: 0 }],
tabs: { tabs: {
opened: [], opened: [],
}, },
}), }),
{ {
name: props.sessionId ?? "new-session", name: seed,
}, },
) )
const [promptStore, setPromptStore] = createStore<{
prompt: Prompt
cursor?: number
}>({
prompt: clonePrompt(DEFAULT_PROMPT),
})
const key = createMemo(() => props.sessionId ?? "new-session")
const [ready, setReady] = createSignal(false)
const prefix = "session-prompt:"
createEffect(
on(
key,
(value) => {
setReady(false)
const record = localStorage.getItem(prefix + value)
if (!record) {
setPromptStore("prompt", clonePrompt(DEFAULT_PROMPT))
setPromptStore("cursor", undefined)
setReady(true)
return
}
const payload = JSON.parse(record) as { prompt?: Prompt; cursor?: number }
const parts = payload.prompt ?? DEFAULT_PROMPT
const cursor = typeof payload.cursor === "number" ? payload.cursor : undefined
setPromptStore("prompt", clonePrompt(parts))
setPromptStore("cursor", cursor)
setReady(true)
},
{ defer: true },
),
)
createEffect(() => {
if (!ready()) return
const value = key()
const isDefault = isPromptEqual(promptStore.prompt, DEFAULT_PROMPT)
if (isDefault && (promptStore.cursor === undefined || promptStore.cursor <= 0)) {
localStorage.removeItem(prefix + value)
return
}
const next = JSON.stringify({
prompt: clonePrompt(promptStore.prompt),
cursor: promptStore.cursor,
})
localStorage.setItem(prefix + value, next)
})
createEffect(() => { createEffect(() => {
if (!props.sessionId) return if (!props.sessionId) return
sync.session.sync(props.sessionId) sync.session.sync(props.sessionId)
@@ -49,8 +97,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
return userMessages()?.at(0) return userMessages()?.at(0)
}) })
const activeMessage = createMemo(() => { const activeMessage = createMemo(() => {
if (!store.messageId) return lastUserMessage() if (!persist.messageId) return lastUserMessage()
return userMessages()?.find((m) => m.id === store.messageId) return userMessages()?.find((m) => m.id === persist.messageId)
}) })
const working = createMemo(() => { const working = createMemo(() => {
if (!props.sessionId) return false if (!props.sessionId) return false
@@ -101,13 +149,14 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
working, working,
diffs, diffs,
prompt: { prompt: {
current: createMemo(() => store.prompt), current: createMemo(() => promptStore.prompt),
cursor: createMemo(() => store.cursorPosition), cursor: createMemo(() => promptStore.cursor),
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)), dirty: createMemo(() => !isPromptEqual(promptStore.prompt, DEFAULT_PROMPT)),
set(prompt: Prompt, cursorPosition?: number) { set(prompt: Prompt, cursorPosition?: number) {
const next = clonePrompt(prompt)
batch(() => { batch(() => {
setStore("prompt", prompt) setPromptStore("prompt", next)
if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition) if (cursorPosition !== undefined) setPromptStore("cursor", cursorPosition)
}) })
}, },
}, },
@@ -117,7 +166,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
last: lastUserMessage, last: lastUserMessage,
active: activeMessage, active: activeMessage,
setActive(id: string | undefined) { setActive(id: string | undefined) {
setStore("messageId", id) setPersist("messageId", id)
}, },
}, },
usage: { usage: {
@@ -126,53 +175,52 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
context, context,
}, },
layout: { layout: {
tabs: store.tabs, tabs: persist.tabs,
setActiveTab(tab: string | undefined) { setActiveTab(tab: string | undefined) {
setStore("tabs", "active", tab) setPersist("tabs", "active", tab)
}, },
setOpenedTabs(tabs: string[]) { setOpenedTabs(tabs: string[]) {
setStore("tabs", "opened", tabs) setPersist("tabs", "opened", tabs)
}, },
async openTab(tab: string) { async openTab(tab: string) {
if (tab === "chat") { if (tab === "chat") {
setStore("tabs", "active", undefined) setPersist("tabs", "active", undefined)
return return
} }
if (tab.startsWith("file://")) { if (tab.startsWith("file://")) {
await local.file.open(tab.replace("file://", "")) await local.file.open(tab.replace("file://", ""))
} }
if (tab !== "review") { if (tab !== "review") {
if (!store.tabs.opened.includes(tab)) { if (!persist.tabs.opened.includes(tab)) {
setStore("tabs", "opened", [...store.tabs.opened, tab]) setPersist("tabs", "opened", [...persist.tabs.opened, tab])
} }
} }
setStore("tabs", "active", tab) setPersist("tabs", "active", tab)
}, },
closeTab(tab: string) { closeTab(tab: string) {
batch(() => { batch(() => {
setStore( setPersist(
"tabs", "tabs",
"opened", "opened",
store.tabs.opened.filter((x) => x !== tab), persist.tabs.opened.filter((x) => x !== tab),
) )
if (store.tabs.active === tab) { if (persist.tabs.active === tab) {
const index = store.tabs.opened.findIndex((f) => f === tab) const index = persist.tabs.opened.findIndex((f) => f === tab)
const previous = store.tabs.opened[Math.max(0, index - 1)] const previous = persist.tabs.opened[Math.max(0, index - 1)]
setStore("tabs", "active", previous) setPersist("tabs", "active", previous)
} }
}) })
}, },
moveTab(tab: string, to: number) { moveTab(tab: string, to: number) {
const index = store.tabs.opened.findIndex((f) => f === tab) const index = persist.tabs.opened.findIndex((f) => f === tab)
if (index === -1) return if (index === -1) return
setStore( setPersist(
"tabs", "tabs",
"opened", "opened",
produce((opened) => { produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0]) opened.splice(to, 0, opened.splice(index, 1)[0])
}), }),
) )
// setStore("node", path, "pinned", true)
}, },
}, },
} }
@@ -215,3 +263,20 @@ export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
} }
return true return true
} }
function cloneSelection(selection?: TextSelection) {
if (!selection) return undefined
return { ...selection }
}
function clonePart(part: ContentPart): ContentPart {
if (part.type === "text") return { ...part }
return {
...part,
selection: cloneSelection(part.selection),
}
}
function clonePrompt(prompt: Prompt): Prompt {
return prompt.map(clonePart)
}

View File

@@ -231,6 +231,12 @@ registerCustomTheme("OpenCode", () => {
foreground: "var(--syntax-type)", foreground: "var(--syntax-type)",
}, },
}, },
{
scope: ["meta.object.member"],
settings: {
foreground: "var(--syntax-primitive)",
},
},
{ {
scope: [ scope: [
"variable.parameter.function", "variable.parameter.function",
@@ -238,7 +244,6 @@ registerCustomTheme("OpenCode", () => {
"meta.block", "meta.block",
"meta.tag.attributes", "meta.tag.attributes",
"entity.name.constant", "entity.name.constant",
"meta.object.member",
"meta.embedded.expression", "meta.embedded.expression",
"meta.template.expression", "meta.template.expression",
"string.other.begin.yaml", "string.other.begin.yaml",