fix(tui): preserve Zed context on terminal focus (#24662)
This commit is contained in:
@@ -6,7 +6,8 @@ import { Filesystem } from "@/util/filesystem"
|
|||||||
import type { EditorSelection } from "./editor"
|
import type { EditorSelection } from "./editor"
|
||||||
|
|
||||||
const ZedEditorRowSchema = z.object({
|
const ZedEditorRowSchema = z.object({
|
||||||
editor_id: z.number(),
|
item_kind: z.string(),
|
||||||
|
editor_id: z.number().nullable(),
|
||||||
workspace_id: z.number(),
|
workspace_id: z.number(),
|
||||||
workspace_paths: z.string().nullable(),
|
workspace_paths: z.string().nullable(),
|
||||||
timestamp: z.string(),
|
timestamp: z.string(),
|
||||||
@@ -20,6 +21,7 @@ const ZedEditorContentsSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
type ZedEditorRow = z.infer<typeof ZedEditorRowSchema>
|
type ZedEditorRow = z.infer<typeof ZedEditorRowSchema>
|
||||||
|
type ZedActiveEditorRow = ZedEditorRow & { item_kind: "Editor"; editor_id: number }
|
||||||
|
|
||||||
export type ZedSelectionResult =
|
export type ZedSelectionResult =
|
||||||
| { type: "selection"; selection: EditorSelection }
|
| { type: "selection"; selection: EditorSelection }
|
||||||
@@ -64,8 +66,9 @@ function queryZedActiveEditor(dbPath: string, cwd: string) {
|
|||||||
const raw = db
|
const raw = db
|
||||||
.query(
|
.query(
|
||||||
`select
|
`select
|
||||||
|
i.kind as item_kind,
|
||||||
e.item_id as editor_id,
|
e.item_id as editor_id,
|
||||||
e.workspace_id as workspace_id,
|
i.workspace_id as workspace_id,
|
||||||
w.paths as workspace_paths,
|
w.paths as workspace_paths,
|
||||||
w.timestamp as timestamp,
|
w.timestamp as timestamp,
|
||||||
e.buffer_path as buffer_path,
|
e.buffer_path as buffer_path,
|
||||||
@@ -74,9 +77,9 @@ function queryZedActiveEditor(dbPath: string, cwd: string) {
|
|||||||
from items i
|
from items i
|
||||||
join panes p on p.pane_id = i.pane_id and p.workspace_id = i.workspace_id
|
join panes p on p.pane_id = i.pane_id and p.workspace_id = i.workspace_id
|
||||||
join workspaces w on w.workspace_id = i.workspace_id
|
join workspaces w on w.workspace_id = i.workspace_id
|
||||||
join editors e on e.item_id = i.item_id and e.workspace_id = i.workspace_id
|
left join editors e on e.item_id = i.item_id and e.workspace_id = i.workspace_id
|
||||||
left join editor_selections s on s.editor_id = e.item_id and s.workspace_id = e.workspace_id
|
left join editor_selections s on s.editor_id = e.item_id and s.workspace_id = e.workspace_id
|
||||||
where i.active = 1 and p.active = 1 and i.kind = 'Editor' and e.buffer_path is not null
|
where i.active = 1 and p.active = 1
|
||||||
order by w.timestamp desc`,
|
order by w.timestamp desc`,
|
||||||
)
|
)
|
||||||
.all()
|
.all()
|
||||||
@@ -93,6 +96,8 @@ function queryZedActiveEditor(dbPath: string, cwd: string) {
|
|||||||
.filter((entry) => entry.score > 0)
|
.filter((entry) => entry.score > 0)
|
||||||
.sort((left, right) => right.score - left.score || right.row.timestamp.localeCompare(left.row.timestamp))[0]?.row
|
.sort((left, right) => right.score - left.score || right.row.timestamp.localeCompare(left.row.timestamp))[0]?.row
|
||||||
if (!row) return { type: "empty" as const }
|
if (!row) return { type: "empty" as const }
|
||||||
|
if (row.item_kind !== "Editor") return { type: "unavailable" as const }
|
||||||
|
if (!isZedActiveEditorRow(row)) return { type: "empty" as const }
|
||||||
return { type: "row" as const, row }
|
return { type: "row" as const, row }
|
||||||
} catch {
|
} catch {
|
||||||
return { type: "unavailable" as const }
|
return { type: "unavailable" as const }
|
||||||
@@ -101,7 +106,7 @@ function queryZedActiveEditor(dbPath: string, cwd: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryZedEditorContents(dbPath: string, row: ZedEditorRow) {
|
function queryZedEditorContents(dbPath: string, row: ZedActiveEditorRow) {
|
||||||
let db: Database | undefined
|
let db: Database | undefined
|
||||||
try {
|
try {
|
||||||
db = new Database(dbPath, { readonly: true })
|
db = new Database(dbPath, { readonly: true })
|
||||||
@@ -123,6 +128,10 @@ function queryZedEditorContents(dbPath: string, row: ZedEditorRow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isZedActiveEditorRow(row: ZedEditorRow): row is ZedActiveEditorRow {
|
||||||
|
return row.item_kind === "Editor" && row.editor_id != null
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveZedDbPath() {
|
export function resolveZedDbPath() {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
process.env.OPENCODE_ZED_DB,
|
process.env.OPENCODE_ZED_DB,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { tmpdir } from "../../fixture/fixture"
|
|||||||
|
|
||||||
type ZedFixtureOptions = {
|
type ZedFixtureOptions = {
|
||||||
workspacePaths?: string | null
|
workspacePaths?: string | null
|
||||||
|
itemKind?: string
|
||||||
|
editor?: boolean
|
||||||
selectionStart?: number | null
|
selectionStart?: number | null
|
||||||
selectionEnd?: number | null
|
selectionEnd?: number | null
|
||||||
}
|
}
|
||||||
@@ -23,12 +25,14 @@ async function writeZedFixture(dir: string, options: ZedFixtureOptions = {}) {
|
|||||||
db.run("create table editor_selections (editor_id integer, workspace_id integer, start integer, end integer)")
|
db.run("create table editor_selections (editor_id integer, workspace_id integer, start integer, end integer)")
|
||||||
db.run("insert into workspaces values (1, ?, ?)", [options.workspacePaths ?? JSON.stringify([dir]), "2026-04-27"])
|
db.run("insert into workspaces values (1, ?, ?)", [options.workspacePaths ?? JSON.stringify([dir]), "2026-04-27"])
|
||||||
db.run("insert into panes values (1, 1, 1)")
|
db.run("insert into panes values (1, 1, 1)")
|
||||||
db.run("insert into items values (1, 1, 1, 1, 'Editor')")
|
db.run("insert into items values (1, 1, 1, 1, ?)", [options.itemKind ?? "Editor"])
|
||||||
db.run("insert into editors values (1, 1, ?, ?)", [filePath, "one\ntwo\nthree"])
|
if (options.editor !== false) {
|
||||||
db.run("insert into editor_selections values (1, 1, ?, ?)", [
|
db.run("insert into editors values (1, 1, ?, ?)", [filePath, "one\ntwo\nthree"])
|
||||||
options.selectionStart === undefined ? 4 : options.selectionStart,
|
db.run("insert into editor_selections values (1, 1, ?, ?)", [
|
||||||
options.selectionEnd === undefined ? 7 : options.selectionEnd,
|
options.selectionStart === undefined ? 4 : options.selectionStart,
|
||||||
])
|
options.selectionEnd === undefined ? 7 : options.selectionEnd,
|
||||||
|
])
|
||||||
|
}
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
return { dbPath, filePath }
|
return { dbPath, filePath }
|
||||||
@@ -68,6 +72,13 @@ test("resolveZedSelection returns empty when no workspace matches", async () =>
|
|||||||
expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "empty" })
|
expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "empty" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("resolveZedSelection returns unavailable when a Zed terminal is active", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
const fixture = await writeZedFixture(tmp.path, { itemKind: "Terminal", editor: false })
|
||||||
|
|
||||||
|
expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "unavailable" })
|
||||||
|
})
|
||||||
|
|
||||||
test("resolveZedSelection returns unavailable when the database cannot be queried", async () => {
|
test("resolveZedSelection returns unavailable when the database cannot be queried", async () => {
|
||||||
await using tmp = await tmpdir()
|
await using tmp = await tmpdir()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user