Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions packages/core/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export * as ProjectV2 from "./project"
export * as Project from "./project"

import { Context, Effect, Layer, Schema } from "effect"
import { eq } from "drizzle-orm"
import { asc, desc, eq } from "drizzle-orm"
import path from "path"
import { AbsolutePath, withStatics } from "./schema"
import { FSUtil } from "./fs-util"
Expand Down Expand Up @@ -76,11 +76,10 @@ export const layer = Layer.effect(
.select({ directory: ProjectDirectoryTable.directory })
.from(ProjectDirectoryTable)
.where(eq(ProjectDirectoryTable.project_id, input.projectID))
.orderBy(desc(ProjectDirectoryTable.time_created), asc(ProjectDirectoryTable.directory))
.all()
.pipe(Effect.orDie)
return rows
.toSorted((a, b) => a.directory.localeCompare(b.directory))
.map((row) => AbsolutePath.make(row.directory))
return rows.map((row) => AbsolutePath.make(row.directory))
})

const cached = Effect.fnUntraced(function* (dir: string) {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/test/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("Project directories schemas", () => {
}),
)

it.effect("lists stored project directories only for the requested project", () =>
it.effect("lists stored project directories newest first for the requested project", () =>
Effect.gen(function* () {
const project = yield* ProjectV2.Service
const { db } = yield* Database.Service
Expand All @@ -82,16 +82,16 @@ describe("Project directories schemas", () => {
yield* db
.insert(ProjectDirectoryTable)
.values([
{ project_id: projectID, directory: AbsolutePath.make("/repo/z"), type: "root" },
{ project_id: projectID, directory: AbsolutePath.make("/repo/a"), type: "main" },
{ project_id: otherID, directory: AbsolutePath.make("/other"), type: "main" },
{ project_id: projectID, directory: AbsolutePath.make("/repo/z"), type: "root", time_created: 2 },
{ project_id: projectID, directory: AbsolutePath.make("/repo/a"), type: "main", time_created: 1 },
{ project_id: otherID, directory: AbsolutePath.make("/other"), type: "main", time_created: 3 },
])
.run()
.pipe(Effect.orDie)

expect(yield* project.directories({ projectID })).toEqual([
AbsolutePath.make("/repo/a"),
AbsolutePath.make("/repo/z"),
AbsolutePath.make("/repo/a"),
])
}),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ const REFRESH_FRAMES = ["■", "⬝"]

export type MoveSessionSelection = { type: "directory"; directory: string } | { type: "new" }

export function DialogMoveSession(props: { projectID: string; onSelect: (selection: MoveSessionSelection) => void }) {
export function DialogMoveSession(props: {
projectID: string
current?: MoveSessionSelection
onSelect: (selection: MoveSessionSelection) => void
}) {
const dialog = useDialog()
const sdk = useSDK()
const dimensions = useTerminalDimensions()
Expand All @@ -42,7 +46,7 @@ export function DialogMoveSession(props: { projectID: string; onSelect: (selecti
},
)

const options = createMemo<DialogSelectOption<string | undefined>[]>(() => {
const options = createMemo<DialogSelectOption<MoveSessionSelection | undefined>[]>(() => {
if (directories.loading) return [{ title: "Loading project directories...", value: undefined }]
if (directories.error) return [{ title: "Failed to load project directories", value: undefined }]
const data = directories()
Expand Down Expand Up @@ -88,7 +92,7 @@ export function DialogMoveSession(props: { projectID: string; onSelect: (selecti
<span style={{ fg: theme.textMuted }}>{visible.slice(split)}</span>
</>
) : undefined,
value: item.location,
value: { type: "directory", directory: item.location } as const,
category: item.root === data?.main ? "Project" : "Working copies",
titleWidth,
truncateTitle: "left" as const,
Expand All @@ -103,8 +107,9 @@ export function DialogMoveSession(props: { projectID: string; onSelect: (selecti
<DialogSelect
title="Move session"
options={options()}
current={props.current}
onSelect={(option) => {
if (option.value) props.onSelect({ type: "directory", directory: option.value })
if (option.value) props.onSelect(option.value)
}}
actions={[
{
Expand Down
3 changes: 3 additions & 0 deletions packages/opencode/src/cli/cmd/tui/component/prompt/move.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
function open() {
const projectID = input.projectID()
if (!projectID) return
const sessionID = input.sessionID()
const session = sessionID ? sync.session.get(sessionID) : undefined
dialog.replace(() => (
<DialogMoveSession
projectID={projectID}
current={homeDestination?.destination() ?? (session ? { type: "directory", directory: session.directory } : undefined)}
onSelect={(selection) => {
const sessionID = input.sessionID()
if (!sessionID) {
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/tui/config/keybind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export const Definitions = {
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
"dialog.prompt.submit": keybind("return", "Submit dialog prompt"),
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
"dialog.move_session.new": keybind("ctrl+w", "New project copy"),
"dialog.move_session.new": keybind("ctrl+m", "New project copy"),
"prompt.autocomplete.prev": keybind("up,ctrl+p", "Move to previous autocomplete item"),
"prompt.autocomplete.next": keybind("down,ctrl+n", "Move to next autocomplete item"),
"prompt.autocomplete.hide": keybind("escape", "Hide autocomplete"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ function Directory(props: { api: TuiPluginApi }) {
const destination = useHomeSessionDestination()
const dir = createMemo(() => {
const selected = destination?.destination()
if (selected?.type === "new") return
if (selected?.type === "directory") return selected.directory.replace(Global.Path.home, "~")
const dir = props.api.state.path.directory || process.cwd()
const out = dir.replace(Global.Path.home, "~")
const branch = props.api.state.vcs?.branch
if (!selected || selected.type === "new") return
const out = selected.directory.replace(Global.Path.home, "~")
const branch =
selected.directory === (props.api.state.path.directory || process.cwd()) ? props.api.state.vcs?.branch : undefined
if (branch) return out + ":" + branch
return out
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext, createSignal, useContext, type Accessor, type ParentProps, type Setter } from "solid-js"
import { createContext, createMemo, createSignal, useContext, type Accessor, type ParentProps, type Setter } from "solid-js"
import { useSync } from "../../context/sync"

export type HomeSessionDestination = { type: "directory"; directory: string } | { type: "new" }

Expand All @@ -11,7 +12,11 @@ type Context = {
const HomeSessionDestinationContext = createContext<Context>()

export function HomeSessionDestinationProvider(props: ParentProps) {
const [destination, setDestination] = createSignal<HomeSessionDestination>()
const sync = useSync()
const [selected, setDestination] = createSignal<HomeSessionDestination>()
const destination = createMemo<HomeSessionDestination>(() =>
selected() ?? { type: "directory", directory: sync.path.directory || process.cwd() },
)
return (
<HomeSessionDestinationContext.Provider
value={{ destination, setDestination, clear: () => setDestination(undefined) }}
Expand Down
Loading
Loading