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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ You can set the VSCode text editor to use an installed Nerd Font by setting `"ed

Once you have a Nerd Font set for your editor font, to use these icons in your oil view, set `"oil-code.hasNerdFont": true`.

## Confirmation Dialog

By default, oil.code uses a modal confirmation dialog when you save file operations. You can enable an alternate confirmation interface by setting `"oil-code.enableAlternateConfirmation": true`.

The alternate confirmation dialog provides a QuickPick interface where you can:

- Type `Y` to confirm and apply changes
- Type `N` to cancel and discard changes
- Press `Esc` or click outside to cancel

## Other great extensions

- [vsnetrw](https://github.com/danprince/vsnetrw): Another great option for a split file explorer.
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@
"type": "boolean",
"default": false,
"description": "Enable workspace edit for file move/rename operations. When enabled, VS Code will ask to update references when a file is moved or renamed. Default is false."
},
"oil-code.enableAlternateConfirmation": {
"type": "boolean",
"default": false,
"description": "Enable alternate confirmation dialog for file operations. When enabled, uses a QuickPick interface instead of the default modal confirmation dialog. Default is false."
}
}
}
Expand Down
114 changes: 75 additions & 39 deletions src/handlers/onDidSaveTextDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
import { select } from "../commands/select";
import { newline } from "../newline";
import { logger } from "../logger";
import { getEnableWorkspaceEditSetting } from "../utils/settings";
import {
getEnableWorkspaceEditSetting,
getEnableAlternateConfirmationSetting,
} from "../utils/settings";
import { confirmChanges, type Change } from "../ui/confirmChanges";

export async function onDidSaveTextDocument(document: vscode.TextDocument) {
// Check if the saved document is our oil file
Expand Down Expand Up @@ -134,46 +138,78 @@ export async function onDidSaveTextDocument(document: vscode.TextDocument) {
oilState.openAfterSave = undefined;
return;
}
// Get the alternate confirmation dialog setting
const useAlternateConfirmation = getEnableAlternateConfirmationSetting();

// Show confirmation dialog
let message = "The following changes will be applied:\n\n";
if (movedLines.length > 0) {
movedLines.forEach((item) => {
const [originalPath, newPath] = item;
message += `MOVE ${formatPath(originalPath)} → ${formatPath(
newPath
)}\n`;
});
}
if (copiedLines.length > 0) {
copiedLines.forEach((item) => {
const [originalPath, newPath] = item;
message += `COPY ${formatPath(originalPath)} → ${formatPath(
newPath
)}\n`;
});
}
if (addedLines.size > 0) {
addedLines.forEach((item) => {
message += `CREATE ${formatPath(item)}\n`;
});
}
if (deletedLines.size > 0) {
deletedLines.forEach((item) => {
message += `DELETE ${formatPath(item)}\n`;
});
}
// Show confirmation dialog
const response = await vscode.window.showWarningMessage(
message,
{ modal: true },
"Yes",
"No"
);
if (response !== "Yes") {
oilState.openAfterSave = undefined;
return;
if (useAlternateConfirmation) {
// Build change list and confirm using Quick Pick
const uiChanges: Change[] = [];
for (const [from, to] of movedLines) {
uiChanges.push({ kind: "move", from, to });
}
for (const [from, to] of copiedLines) {
uiChanges.push({ kind: "copy", from, to });
}
for (const p of addedLines) {
uiChanges.push({ kind: "create", to: p });
}
for (const p of deletedLines) {
uiChanges.push({ kind: "delete", from: p });
}
const ok = await confirmChanges(
uiChanges.map((c) => ({
// format paths to relative for nicer display (but still keep full for ops later)
...(c as any),
from: "from" in c ? formatPath((c as any).from) : undefined,
to: "to" in c ? formatPath((c as any).to) : undefined,
})) as Change[]
);
if (!ok) {
oilState.openAfterSave = undefined;
return;
}
} else {
// Show confirmation dialog
let message = "The following changes will be applied:\n\n";
if (movedLines.length > 0) {
movedLines.forEach((item) => {
const [originalPath, newPath] = item;
message += `MOVE ${formatPath(originalPath)} → ${formatPath(
newPath
)}\n`;
});
}
if (copiedLines.length > 0) {
copiedLines.forEach((item) => {
const [originalPath, newPath] = item;
message += `COPY ${formatPath(originalPath)} → ${formatPath(
newPath
)}\n`;
});
}
if (addedLines.size > 0) {
addedLines.forEach((item) => {
message += `CREATE ${formatPath(item)}\n`;
});
}
if (deletedLines.size > 0) {
deletedLines.forEach((item) => {
message += `DELETE ${formatPath(item)}\n`;
});
}
// Show confirmation dialog
const response = await vscode.window.showWarningMessage(
message,
{ modal: true },
"Yes",
"No"
);
if (response !== "Yes") {
oilState.openAfterSave = undefined;
return;
}
}

logger.debug("Processing changes...");

// Delete files/directories
Expand Down
121 changes: 121 additions & 0 deletions src/ui/confirmChanges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as vscode from "vscode";

export type Change =
| { kind: "create"; to: string }
| { kind: "delete"; from: string }
| { kind: "rename" | "move"; from: string; to: string }
| { kind: "modify"; from: string }
| { kind: "copy"; from: string; to: string };

export async function confirmChanges(changes: Change[]): Promise<boolean> {
if (changes.length === 0) {
return true;
}
return confirmChangesQuickPick(changes);
}

async function confirmChangesQuickPick(changes: Change[]): Promise<boolean> {
const qp = vscode.window.createQuickPick<vscode.QuickPickItem>();
qp.title = "oil.code — Confirm changes";
qp.matchOnDetail = true;

// Outside click should cancel -> allow hide on blur
qp.ignoreFocusOut = false;

// Read-only list feel
qp.items = changes.map(toQuickPickItem);
qp.canSelectMany = false;

// "Hide" the input and instruct the user
qp.placeholder = "[Y]es [N]o";
qp.value = "";

// We don't want buttons; Y/N only
qp.buttons = [];

const disposables: vscode.Disposable[] = [];
const decision = await new Promise<boolean>((resolve) => {
let finished = false;
const finish = (ok: boolean) => {
if (finished) {
return;
}
finished = true;
try {
qp.hide();
} catch {}
resolve(ok);
};

// Make rows feel non-interactive
disposables.push(
qp.onDidChangeSelection(() => {
qp.selectedItems = [];
}),
qp.onDidChangeActive(() => {
qp.activeItems = [];
}),

// Ignore Enter entirely (only Y/N should close)
qp.onDidAccept(() => {
/* no-op */
}),

// Capture last typed char; accept only Y or N
qp.onDidChangeValue((val) => {
const ch = val.trim().slice(-1).toLowerCase();
qp.value = ""; // keep the field visually empty
if (ch === "y") {
return finish(true);
}
if (ch === "n") {
return finish(false);
}
}),

// Esc or outside click hides -> cancel
qp.onDidHide(() => {
if (!finished) {
resolve(false);
}
})
);

qp.show();
});

disposables.forEach((d) => d.dispose());
qp.dispose();
return decision;
}

function toQuickPickItem(c: Change): vscode.QuickPickItem {
switch (c.kind) {
case "create":
return {
label: "$(diff-added) create",
detail: c.to,
};
case "delete":
return {
label: "$(diff-removed) delete",
detail: c.from,
};
case "modify":
return {
label: "$(edit) modify",
detail: c.from,
};
case "rename":
case "move":
return {
label: `$(diff-renamed) ${c.kind}`,
detail: `${c.from} \u2192 ${c.to}`,
};
case "copy":
return {
label: "$(diff-added) copy",
detail: `${c.from} \u2192 ${c.to}`,
};
}
}
5 changes: 5 additions & 0 deletions src/utils/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export function getEnableWorkspaceEditSetting(): boolean {
return config.get<boolean>("enableWorkspaceEdit") || false;
}

export function getEnableAlternateConfirmationSetting(): boolean {
const config = vscode.workspace.getConfiguration("oil-code");
return config.get<boolean>("enableAlternateConfirmation") || false;
}

let restoreAutoSave = false;

export async function checkAndDisableAutoSave() {
Expand Down
Loading