diff --git a/shared/src/types/index.ts b/shared/src/types/index.ts index a768abbc..532c18f4 100644 --- a/shared/src/types/index.ts +++ b/shared/src/types/index.ts @@ -89,6 +89,7 @@ export interface LocalChange { modifiedUri: Uri; originalUri: Uri; diff: string; + state: "pending" | "applied" | "discarded"; } export interface ResolutionMessage { diff --git a/vscode/src/commands.ts b/vscode/src/commands.ts index e9f33679..c6941c1e 100644 --- a/vscode/src/commands.ts +++ b/vscode/src/commands.ts @@ -336,23 +336,23 @@ const commandsMap: (state: ExtensionState) => { // Update the user settings await config.update("labelSelector", modifiedLabelSelector, ConfigurationTarget.Workspace); }, - "konveyor.loadRuleSets": (ruleSets: RuleSet[]): void => loadRuleSets(state, ruleSets), + "konveyor.loadRuleSets": async (ruleSets: RuleSet[]) => loadRuleSets(state, ruleSets), "konveyor.cleanRuleSets": () => cleanRuleSets(state), "konveyor.loadStaticResults": loadStaticResults, "konveyor.loadResultsFromDataFolder": loadResultsFromDataFolder, "konveyor.loadSolution": async (solution: GetSolutionResult) => loadSolution(state, solution), - "konveyor.applyAll": () => applyAll(state), - "konveyor.applyFile": (item: FileItem | Uri) => applyFile(item, state), - "konveyor.copyDiff": (item: FileItem | Uri) => copyDiff(item, state), + "konveyor.applyAll": async () => applyAll(state), + "konveyor.applyFile": async (item: FileItem | Uri) => applyFile(item, state), + "konveyor.copyDiff": async (item: FileItem | Uri) => copyDiff(item, state), "konveyor.copyPath": copyPath, "konveyor.diffView.viewFix": viewFix, - "konveyor.discardAll": () => discardAll(state), - "konveyor.discardFile": (item: FileItem | Uri) => discardFile(item, state), + "konveyor.discardAll": async () => discardAll(state), + "konveyor.discardFile": async (item: FileItem | Uri) => discardFile(item, state), "konveyor.showResolutionPanel": () => { const resolutionProvider = state.webviewProviders?.get("resolution"); resolutionProvider?.showWebviewPanel(); }, - "konveyor.reloadLastResolutions": () => reloadLastResolutions(state), + "konveyor.reloadLastResolutions": async () => reloadLastResolutions(state), "konveyor.diffView.applyBlock": applyBlock, "konveyor.diffView.applyBlockInline": applyBlock, "konveyor.diffView.applySelection": applyBlock, diff --git a/vscode/src/data/loadResults.ts b/vscode/src/data/loadResults.ts index da24d784..ee8e6fac 100644 --- a/vscode/src/data/loadResults.ts +++ b/vscode/src/data/loadResults.ts @@ -3,16 +3,15 @@ import { processIncidents } from "./analyzerResults"; import { ExtensionState } from "src/extensionState"; import { writeDataFile } from "./storage"; import { toLocalChanges, writeSolutionsToMemFs } from "./virtualStorage"; -import { Location, Position, window } from "vscode"; +import { window } from "vscode"; import { KONVEYOR_SCHEME, RULE_SET_DATA_FILE_PREFIX, SOLUTION_DATA_FILE_PREFIX, } from "../utilities"; -import { Immutable } from "immer"; -export const loadRuleSets = (state: ExtensionState, ruleSets: RuleSet[]): void => { - writeDataFile(ruleSets, RULE_SET_DATA_FILE_PREFIX); +export const loadRuleSets = async (state: ExtensionState, ruleSets: RuleSet[]) => { + await writeDataFile(ruleSets, RULE_SET_DATA_FILE_PREFIX); state.diagnosticCollection.set(processIncidents(ruleSets)); state.mutateData((draft) => { draft.ruleSets = ruleSets; @@ -26,24 +25,23 @@ export const cleanRuleSets = (state: ExtensionState) => { }; export const loadSolution = async (state: ExtensionState, solution: GetSolutionResult) => { - writeDataFile(solution, SOLUTION_DATA_FILE_PREFIX); - const localChanges = toLocalChanges(solution); - doLoadSolution(state, localChanges); - state.mutateData((draft) => { - draft.localChanges = localChanges; - }); + await writeDataFile(solution, SOLUTION_DATA_FILE_PREFIX); + await doLoadSolution(state, toLocalChanges(solution)); }; export const reloadLastResolutions = async (state: ExtensionState) => { - doLoadSolution(state, state.data.localChanges); + await doLoadSolution( + state, + state.data.localChanges.map((it) => ({ ...it, state: "pending" })), + ); + window.showInformationMessage(`Loaded last available resolutions`); }; -const doLoadSolution = async (state: ExtensionState, localChanges: Immutable) => { +const doLoadSolution = async (state: ExtensionState, localChanges: LocalChange[]) => { state.memFs.removeAll(KONVEYOR_SCHEME); await writeSolutionsToMemFs(localChanges, state); - const locations = localChanges.map( - ({ originalUri: uri }) => new Location(uri, new Position(0, 0)), - ); - state.fileModel.updateLocations(locations); + state.mutateData((draft) => { + draft.localChanges = localChanges; + }); }; diff --git a/vscode/src/data/virtualStorage.ts b/vscode/src/data/virtualStorage.ts index 16035f78..507b6e96 100644 --- a/vscode/src/data/virtualStorage.ts +++ b/vscode/src/data/virtualStorage.ts @@ -7,7 +7,7 @@ import path from "path"; import { KONVEYOR_SCHEME, fromRelativeToKonveyor } from "../utilities"; import { Immutable } from "immer"; -export const toLocalChanges = (solution: GetSolutionResult) => +export const toLocalChanges = (solution: GetSolutionResult): LocalChange[] => solution.changes.map(({ modified, original, diff }) => ({ modifiedUri: fromRelativeToKonveyor(modified), originalUri: Uri.from({ @@ -15,6 +15,7 @@ export const toLocalChanges = (solution: GetSolutionResult) => path: path.join(workspace.workspaceFolders?.[0].uri.fsPath ?? "", original), }), diff, + state: "pending", })); export const writeSolutionsToMemFs = async ( diff --git a/vscode/src/diffView/register.ts b/vscode/src/diffView/register.ts index cb7a05d7..6104c4fe 100644 --- a/vscode/src/diffView/register.ts +++ b/vscode/src/diffView/register.ts @@ -1,15 +1,17 @@ import * as vscode from "vscode"; import { KonveyorTreeDataProvider } from "./fileModel"; import { Navigation } from "./navigation"; -import { ExtensionState } from "src/extensionState"; +import { ExtensionData, ExtensionState } from "src/extensionState"; import { KONVEYOR_READ_ONLY_SCHEME, KONVEYOR_SCHEME } from "../utilities"; import KonveyorReadOnlyProvider from "../data/readOnlyStorage"; +import { Immutable } from "immer"; +import { LocalChange } from "@editor-extensions/shared"; export function registerDiffView({ extensionContext: context, memFs, fileModel: model, -}: ExtensionState): void { +}: ExtensionState): (data: Immutable) => void { context.subscriptions.push( vscode.workspace.registerFileSystemProvider(KONVEYOR_SCHEME, memFs, { isCaseSensitive: true, @@ -39,4 +41,35 @@ export function registerDiffView({ readOnlyProvider, ), ); + + const lastLocalChanges: LocalChange[] = []; + return async (data: Immutable) => { + const locations = data.localChanges + .filter((change) => change.state === "pending") + .map(({ originalUri: uri }) => new vscode.Location(uri, new vscode.Position(0, 0))); + model.updateLocations(locations); + + const hasChanged = (it: unknown, index: number) => lastLocalChanges[index] !== it; + const copyFromTo = (change: LocalChange) => + change.state === "discarded" + ? [change.originalUri, change.modifiedUri] + : [change.modifiedUri, change.originalUri]; + + await Promise.all( + data.localChanges + .map((change, index): [LocalChange, number] => [change, index]) + .filter(([change, index]) => hasChanged(change, index)) + .filter(([{ state }]) => state === "applied" || state === "discarded") + .map(([change, index]): [LocalChange, number, vscode.Uri[]] => [ + change, + index, + copyFromTo(change), + ]) + .map(([change, index, [fromUri, toUri]]) => + vscode.workspace.fs.copy(fromUri, toUri, { overwrite: true }).then(() => { + lastLocalChanges[index] = change; + }), + ), + ); + }; } diff --git a/vscode/src/diffView/solutionCommands.ts b/vscode/src/diffView/solutionCommands.ts index ed30e4e4..1030ffcb 100644 --- a/vscode/src/diffView/solutionCommands.ts +++ b/vscode/src/diffView/solutionCommands.ts @@ -2,32 +2,26 @@ import * as vscode from "vscode"; import { ExtensionState } from "src/extensionState"; import { fromRelativeToKonveyor, KONVEYOR_READ_ONLY_SCHEME } from "../utilities"; import { FileItem, toUri } from "./fileModel"; +import { LocalChange } from "@editor-extensions/shared"; +import { Immutable } from "immer"; export const applyAll = async (state: ExtensionState) => { const localChanges = state.data.localChanges; - await Promise.all( - localChanges.map(({ originalUri, modifiedUri }) => - vscode.workspace.fs.copy(modifiedUri, originalUri, { overwrite: true }), - ), - ); - const sidebarProvider = state.webviewProviders?.get("sidebar"); - sidebarProvider?.webview?.postMessage({ - type: "solutionConfirmation", - data: { confirmed: true, solution: null }, + + state.mutateData((draft) => { + draft.localChanges = localChanges.map((it) => ({ ...it, state: "applied" })); }); - //TODO: need to keep solutions view and analysis view in sync based on these actions + vscode.window.showInformationMessage(`All resolutions applied successfully`); - state.fileModel.updateLocations([]); }; export const discardAll = async (state: ExtensionState) => { const localChanges = state.data.localChanges; - await Promise.all( - localChanges.map(({ originalUri, modifiedUri }) => - vscode.workspace.fs.copy(originalUri, modifiedUri, { overwrite: true }), - ), - ); - state.fileModel.updateLocations([]); + + state.mutateData((draft) => { + draft.localChanges = localChanges.map((it) => ({ ...it, state: "discarded" })); + }); + vscode.window.showInformationMessage(`Discarded all resolutions`); }; @@ -64,15 +58,12 @@ export const viewFixInDiffEditor = async (uri: vscode.Uri, preserveFocus: boolea ); export const applyFile = async (item: FileItem | vscode.Uri | unknown, state: ExtensionState) => { - const originalUri = toUri(item); - if (!originalUri) { - vscode.window.showErrorMessage("Failed to apply changes"); - console.error("Failed to apply changes", item, originalUri); - return; + const index = getChangeIndex(item, "Failed to apply changes", state.data.localChanges); + if (index > -1) { + state.mutateData((draft) => { + draft.localChanges[index].state = "applied"; + }); } - const modifiedUri = fromRelativeToKonveyor(vscode.workspace.asRelativePath(originalUri)); - await vscode.workspace.fs.copy(modifiedUri, originalUri, { overwrite: true }); - state.fileModel.markedAsApplied(originalUri); }; interface ApplyBlockArgs { @@ -102,13 +93,33 @@ export const applyBlock = async ({ originalUri, originalWithModifiedChanges }: A }; export const discardFile = async (item: FileItem | vscode.Uri | unknown, state: ExtensionState) => { + const index = getChangeIndex(item, "Failed to discard changes", state.data.localChanges); + if (index > -1) { + state.mutateData((draft) => { + draft.localChanges[index].state = "discarded"; + }); + } +}; + +const getChangeIndex = ( + item: FileItem | vscode.Uri | unknown, + errorMsg: string, + localChanges: Immutable, +) => { const originalUri = toUri(item); if (!originalUri) { - vscode.window.showErrorMessage("Failed to discard changes"); - console.error("Failed to discard changes", item, originalUri); - return; + vscode.window.showErrorMessage(`${errorMsg}(unknown URI)`); + console.error(`${errorMsg}(unknown URI)`, item, originalUri); + return -1; + } + const index = localChanges.findIndex( + (it) => it.originalUri.toString() === originalUri.toString(), + ); + + if (index < 0) { + vscode.window.showErrorMessage(`${errorMsg}(unknown index)`); + console.error(`${errorMsg}(unknown index)`, item, originalUri); + return -1; } - const modifiedUri = fromRelativeToKonveyor(vscode.workspace.asRelativePath(originalUri)); - await vscode.workspace.fs.copy(originalUri, modifiedUri, { overwrite: true }); - state.fileModel.markedAsApplied(originalUri); + return index; }; diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 5d438f8a..c0c3b530 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -47,7 +47,7 @@ class VsCodeExtension { try { this.checkWorkspace(); this.registerWebviewProvider(context); - registerDiffView(this.state); + this.onDidChangeData(registerDiffView(this.state)); this.registerCommands(); this.registerLanguageProviders(context); vscode.commands.executeCommand("konveyor.loadResultsFromDataFolder");