Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Use immer for extension state #107

Merged
merged 2 commits into from
Nov 21, 2024
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
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"js-yaml": "^4.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"uuid": "^10.0.0"
"uuid": "^10.0.0",
"immer": "10.1.1"
}
}
1 change: 1 addition & 0 deletions shared/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export interface LocalChange {
modifiedUri: Uri;
originalUri: Uri;
diff: string;
state: "pending" | "applied" | "discarded";
}

export interface ResolutionMessage {
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/KonveyorGUIWebviewViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class KonveyorGUIWebviewViewProvider implements WebviewViewProvider {
this.initializeWebview(this._panel.webview);

if (this._viewType === KonveyorGUIWebviewViewProvider.RESOLUTION_VIEW_TYPE) {
const savedData = this._extensionState.sharedState.get("resolutionPanelData");
const savedData = this._extensionState.data.resolutionPanelData;
if (savedData) {
this._panel.webview.postMessage({ type: "loadResolutionState", data: savedData });
}
Expand Down
14 changes: 7 additions & 7 deletions vscode/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
39 changes: 17 additions & 22 deletions vscode/src/data/loadResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,45 @@ 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";

export const loadRuleSets = (state: ExtensionState, ruleSets: RuleSet[]): void => {
writeDataFile(ruleSets, RULE_SET_DATA_FILE_PREFIX);
state.ruleSets = ruleSets;
export const loadRuleSets = async (state: ExtensionState, ruleSets: RuleSet[]) => {
await writeDataFile(ruleSets, RULE_SET_DATA_FILE_PREFIX);
state.diagnosticCollection.set(processIncidents(ruleSets));
const sidebarProvider = state.webviewProviders?.get("sidebar");
sidebarProvider?.webview?.postMessage({
type: "loadStoredAnalysis",
data: ruleSets,
state.mutateData((draft) => {
draft.ruleSets = ruleSets;
});
};
export const cleanRuleSets = (state: ExtensionState) => {
state.ruleSets = [];
state.diagnosticCollection.clear();
const sidebarProvider = state.webviewProviders?.get("sidebar");
sidebarProvider?.webview?.postMessage({
type: "loadStoredAnalysis",
data: undefined,
state.mutateData((draft) => {
draft.ruleSets = [];
});
};

export const loadSolution = async (state: ExtensionState, solution: GetSolutionResult) => {
writeDataFile(solution, SOLUTION_DATA_FILE_PREFIX);
const localChanges = toLocalChanges(solution);
doLoadSolution(state, localChanges);
state.localChanges = localChanges;
await writeDataFile(solution, SOLUTION_DATA_FILE_PREFIX);
await doLoadSolution(state, toLocalChanges(solution));
};

export const reloadLastResolutions = async (state: ExtensionState) => {
doLoadSolution(state, state.localChanges);
await doLoadSolution(
state,
state.data.localChanges.map((it) => ({ ...it, state: "pending" })),
);

window.showInformationMessage(`Loaded last available resolutions`);
};

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;
});
};
6 changes: 4 additions & 2 deletions vscode/src/data/virtualStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import * as Diff from "diff";
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({
scheme: "file",
path: path.join(workspace.workspaceFolders?.[0].uri.fsPath ?? "", original),
}),
diff,
state: "pending",
}));

export const writeSolutionsToMemFs = async (
localChanges: LocalChange[],
localChanges: Immutable<LocalChange[]>,
{ memFs }: ExtensionState,
) => {
// TODO: implement logic for deleted/added files
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/diffView/copyCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FileItem, toUri } from "./fileModel";
import { ExtensionState } from "src/extensionState";

export async function copyDiff(item: FileItem | vscode.Uri | unknown, state: ExtensionState) {
const localChanges = state.localChanges;
const localChanges = state.data.localChanges;
const uri = toUri(item);
if (!uri) {
console.error("Failed to copy diff. Unknown URI.", item, uri);
Expand Down
37 changes: 35 additions & 2 deletions vscode/src/diffView/register.ts
Original file line number Diff line number Diff line change
@@ -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<ExtensionData>) => void {
context.subscriptions.push(
vscode.workspace.registerFileSystemProvider(KONVEYOR_SCHEME, memFs, {
isCaseSensitive: true,
Expand Down Expand Up @@ -39,4 +41,35 @@ export function registerDiffView({
readOnlyProvider,
),
);

const lastLocalChanges: LocalChange[] = [];
return async (data: Immutable<ExtensionData>) => {
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;
}),
),
);
};
}
77 changes: 44 additions & 33 deletions vscode/src/diffView/solutionCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.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 },
const localChanges = state.data.localChanges;

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.localChanges;
await Promise.all(
localChanges.map(({ originalUri, modifiedUri }) =>
vscode.workspace.fs.copy(originalUri, modifiedUri, { overwrite: true }),
),
);
state.fileModel.updateLocations([]);
const localChanges = state.data.localChanges;

state.mutateData((draft) => {
draft.localChanges = localChanges.map((it) => ({ ...it, state: "discarded" }));
});

vscode.window.showInformationMessage(`Discarded all resolutions`);
};

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<LocalChange[]>,
) => {
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;
};
Loading
Loading