Skip to content
Draft
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
17 changes: 17 additions & 0 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class DiffViewProvider {
originalContent: string | undefined
private createdDirs: string[] = []
private documentWasOpen = false
private documentWasPinned = false
private relPath?: string
private newContent?: string
private activeDiffEditor?: vscode.TextEditor
Expand Down Expand Up @@ -84,6 +85,7 @@ export class DiffViewProvider {
// If the file was already open, close it (must happen after showing the
// diff view since if it's the only tab the column will close).
this.documentWasOpen = false
this.documentWasPinned = false

// Close the tab if it's open (it's already saved above).
const tabs = vscode.window.tabGroups.all
Expand All @@ -97,6 +99,9 @@ export class DiffViewProvider {
)

for (const tab of tabs) {
if (tab.isPinned) {
this.documentWasPinned = true
}
if (!tab.isDirty) {
await vscode.window.tabGroups.close(tab)
}
Expand Down Expand Up @@ -210,6 +215,12 @@ export class DiffViewProvider {
}

await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false, preserveFocus: true })

// Restore pinned state if the tab was pinned before editing.
if (this.documentWasPinned) {
await vscode.commands.executeCommand("workbench.action.pinEditor")
}

await this.closeAllDiffViews()

// Getting diagnostics before and after the file edit is a better approach than
Expand Down Expand Up @@ -401,6 +412,11 @@ export class DiffViewProvider {
preview: false,
preserveFocus: true,
})

// Restore pinned state if the tab was pinned before editing.
if (this.documentWasPinned) {
await vscode.commands.executeCommand("workbench.action.pinEditor")
}
}

await this.closeAllDiffViews()
Expand Down Expand Up @@ -619,6 +635,7 @@ export class DiffViewProvider {
this.originalContent = undefined
this.createdDirs = []
this.documentWasOpen = false
this.documentWasPinned = false
this.activeDiffEditor = undefined
this.fadedOverlayController = undefined
this.activeLineController = undefined
Expand Down
208 changes: 207 additions & 1 deletion src/integrations/editor/__tests__/DiffViewProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ vi.mock("vscode", () => ({
visibleTextEditors: [],
},
commands: {
executeCommand: vi.fn(),
executeCommand: vi.fn().mockResolvedValue(undefined),
},
languages: {
getDiagnostics: vi.fn(() => []),
Expand Down Expand Up @@ -84,6 +84,7 @@ vi.mock("vscode", () => ({
TextEditorRevealType: {
InCenter: 2,
},
TabInputText: class TabInputText {},
TabInputTextDiff: class TabInputTextDiff {},
Uri: {
file: vi.fn((path) => ({ fsPath: path })),
Expand Down Expand Up @@ -517,4 +518,209 @@ describe("DiffViewProvider", () => {
expect(vscode.languages.getDiagnostics).toHaveBeenCalled()
})
})

describe("pinned tab preservation", () => {
describe("open method - pin state capture", () => {
it("should capture pinned state when closing a pinned tab", async () => {
const absolutePath = `${mockCwd}/test.txt`

// Setup a pinned tab
const pinnedTab = {
input: { uri: { scheme: "file", fsPath: absolutePath } },
isDirty: false,
isPinned: true,
}

// Make the tab look like a TabInputText instance
Object.setPrototypeOf(pinnedTab.input, vscode.TabInputText?.prototype ?? {})

Object.defineProperty(vscode.window.tabGroups, "all", {
get: () => [{ tabs: [pinnedTab] }],
configurable: true,
})

vi.mocked(vscode.window.tabGroups.close).mockResolvedValue(true as any)

// Mock for openDiffEditor
const mockEditor = {
document: {
uri: { fsPath: absolutePath, scheme: "file" },
getText: vi.fn().mockReturnValue(""),
lineCount: 0,
},
selection: { active: { line: 0, character: 0 }, anchor: { line: 0, character: 0 } },
edit: vi.fn().mockResolvedValue(true),
revealRange: vi.fn(),
}

vi.mocked(vscode.window.showTextDocument).mockResolvedValue(mockEditor as any)

vi.mocked(vscode.workspace.onDidOpenTextDocument).mockImplementation((callback) => {
setTimeout(() => {
callback({ uri: { fsPath: absolutePath, scheme: "file" } } as any)
}, 0)
return { dispose: vi.fn() }
})

vi.mocked(vscode.window).visibleTextEditors = [mockEditor as any]
;(diffViewProvider as any).editType = "modify"

await diffViewProvider.open("test.txt")

expect((diffViewProvider as any).documentWasPinned).toBe(true)
expect((diffViewProvider as any).documentWasOpen).toBe(true)
})

it("should not set pinned state when closing an unpinned tab", async () => {
const absolutePath = `${mockCwd}/test.txt`

const unpinnedTab = {
input: { uri: { scheme: "file", fsPath: absolutePath } },
isDirty: false,
isPinned: false,
}

Object.setPrototypeOf(unpinnedTab.input, vscode.TabInputText?.prototype ?? {})

Object.defineProperty(vscode.window.tabGroups, "all", {
get: () => [{ tabs: [unpinnedTab] }],
configurable: true,
})

vi.mocked(vscode.window.tabGroups.close).mockResolvedValue(true as any)

const mockEditor = {
document: {
uri: { fsPath: absolutePath, scheme: "file" },
getText: vi.fn().mockReturnValue(""),
lineCount: 0,
},
selection: { active: { line: 0, character: 0 }, anchor: { line: 0, character: 0 } },
edit: vi.fn().mockResolvedValue(true),
revealRange: vi.fn(),
}

vi.mocked(vscode.window.showTextDocument).mockResolvedValue(mockEditor as any)

vi.mocked(vscode.workspace.onDidOpenTextDocument).mockImplementation((callback) => {
setTimeout(() => {
callback({ uri: { fsPath: absolutePath, scheme: "file" } } as any)
}, 0)
return { dispose: vi.fn() }
})

vi.mocked(vscode.window).visibleTextEditors = [mockEditor as any]
;(diffViewProvider as any).editType = "modify"

await diffViewProvider.open("test.txt")

expect((diffViewProvider as any).documentWasPinned).toBe(false)
expect((diffViewProvider as any).documentWasOpen).toBe(true)
})
})

describe("saveChanges - pin state restoration", () => {
beforeEach(() => {
;(diffViewProvider as any).relPath = "test.ts"
;(diffViewProvider as any).newContent = "new content"
;(diffViewProvider as any).activeDiffEditor = {
document: {
getText: vi.fn().mockReturnValue("new content"),
isDirty: false,
save: vi.fn().mockResolvedValue(undefined),
},
}
;(diffViewProvider as any).preDiagnostics = []
;(diffViewProvider as any).closeAllDiffViews = vi.fn().mockResolvedValue(undefined)

vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any)
vi.mocked(vscode.languages.getDiagnostics).mockReturnValue([])
vi.mocked(vscode.commands.executeCommand).mockResolvedValue(undefined)
})

it("should pin the editor after saving when documentWasPinned is true", async () => {
;(diffViewProvider as any).documentWasPinned = true

await diffViewProvider.saveChanges(false)

expect(vscode.commands.executeCommand).toHaveBeenCalledWith("workbench.action.pinEditor")
})

it("should not pin the editor after saving when documentWasPinned is false", async () => {
;(diffViewProvider as any).documentWasPinned = false

await diffViewProvider.saveChanges(false)

expect(vscode.commands.executeCommand).not.toHaveBeenCalledWith("workbench.action.pinEditor")
})
})

describe("revertChanges - pin state restoration", () => {
beforeEach(() => {
;(diffViewProvider as any).relPath = "test.ts"
;(diffViewProvider as any).newContent = "new content"
;(diffViewProvider as any).originalContent = "original content"
;(diffViewProvider as any).editType = "modify"
;(diffViewProvider as any).activeDiffEditor = {
document: {
uri: { fsPath: `${mockCwd}/test.ts` },
getText: vi.fn().mockReturnValue("new content"),
positionAt: vi.fn().mockReturnValue({ line: 0, character: 0 }),
isDirty: false,
save: vi.fn().mockResolvedValue(undefined),
},
}

vi.mocked(vscode.workspace.applyEdit).mockResolvedValue(true)
vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any)
vi.mocked(vscode.commands.executeCommand).mockResolvedValue(undefined)

// Mock closeAllDiffViews
;(diffViewProvider as any).closeAllDiffViews = vi.fn().mockResolvedValue(undefined)
})

it("should pin the editor after reverting when documentWasPinned is true", async () => {
;(diffViewProvider as any).documentWasOpen = true
;(diffViewProvider as any).documentWasPinned = true

await diffViewProvider.revertChanges()

expect(vscode.commands.executeCommand).toHaveBeenCalledWith("workbench.action.pinEditor")
})

it("should not pin the editor after reverting when documentWasPinned is false", async () => {
;(diffViewProvider as any).documentWasOpen = true
;(diffViewProvider as any).documentWasPinned = false

await diffViewProvider.revertChanges()

expect(vscode.commands.executeCommand).not.toHaveBeenCalledWith("workbench.action.pinEditor")
})

it("should not pin the editor when document was not open", async () => {
;(diffViewProvider as any).documentWasOpen = false
;(diffViewProvider as any).documentWasPinned = true

await diffViewProvider.revertChanges()

expect(vscode.commands.executeCommand).not.toHaveBeenCalledWith("workbench.action.pinEditor")
})
})

describe("reset - pin state cleanup", () => {
it("should reset documentWasPinned to false", async () => {
;(diffViewProvider as any).documentWasPinned = true

// Mock tabGroups.all to return empty for closeAllDiffViews
Object.defineProperty(vscode.window.tabGroups, "all", {
get: () => [],
configurable: true,
})

await (diffViewProvider as any).reset()

expect((diffViewProvider as any).documentWasPinned).toBe(false)
})
})
})
})
Loading