Skip to content
Open
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
5 changes: 5 additions & 0 deletions vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { activateReporter, reportEvent, analytics } from "./analytics";
import { showFirstRunPrivacyNotice, showPrivacySettings } from "./privacyNotice";

import { Template, getTemplates, scaffoldTemplate } from "./templateUtils";
import { registerFlydeFileRenameHandler } from "./flyde-file-renames";

// the application insights key (also known as instrumentation key)

Expand Down Expand Up @@ -55,6 +56,10 @@ export function activate(context: vscode.ExtensionContext) {
const mainOutputChannel = vscode.window.createOutputChannel("Flyde");
const debugOutputChannel = vscode.window.createOutputChannel("Flyde (Debug)");

context.subscriptions.push(
registerFlydeFileRenameHandler(mainOutputChannel)
);

let currentTheme = vscode.window.activeColorTheme;

let useDarkMode =
Expand Down
86 changes: 86 additions & 0 deletions vscode/src/flyde-file-rename-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as path from "path";
import { FlydeFlow, NodeInstance, VisualNode } from "@flyde/core";

export type RenamePair = {
oldPath: string;
newPath: string;
};

export function rewriteRenamedFileReferences(
flow: FlydeFlow,
flowPath: string,
renamePairs: RenamePair[]
): boolean {
return rewriteNodeReferences(flow.node, path.dirname(flowPath), renamePairs);
}

function rewriteNodeReferences(
node: VisualNode,
flowDir: string,
renamePairs: RenamePair[]
): boolean {
let changed = false;

for (const instance of node.instances) {
changed = rewriteInstanceReference(instance, flowDir, renamePairs) || changed;
}

return changed;
}

function rewriteInstanceReference(
instance: NodeInstance,
flowDir: string,
renamePairs: RenamePair[]
): boolean {
if (!instance.source) {
return false;
}

if (instance.source.type === "inline" && instance.source.data) {
return rewriteNodeReferences(instance.source.data, flowDir, renamePairs);
}

if (instance.source.type !== "file" || typeof instance.source.data !== "string") {
return false;
}

const existingPath = instance.source.data;
const absoluteReference = path.isAbsolute(existingPath)
? path.normalize(existingPath)
: path.resolve(flowDir, existingPath);
const renamedReference = findRenamedPath(absoluteReference, renamePairs);

if (!renamedReference) {
return false;
}

instance.source.data = path.isAbsolute(existingPath)
? renamedReference
: normalizeImportPath(path.relative(flowDir, renamedReference));

return instance.source.data !== existingPath;
}

function findRenamedPath(
absoluteReference: string,
renamePairs: RenamePair[]
): string | undefined {
for (const renamePair of renamePairs) {
const oldPath = path.normalize(renamePair.oldPath);
const relativeToOld = path.relative(oldPath, absoluteReference);

if (
relativeToOld === "" ||
(relativeToOld &&
!relativeToOld.startsWith("..") &&
!path.isAbsolute(relativeToOld))
) {
return path.join(path.normalize(renamePair.newPath), relativeToOld);
}
}
}

function normalizeImportPath(importPath: string): string {
return importPath.split(path.sep).join("/");
}
65 changes: 65 additions & 0 deletions vscode/src/flyde-file-renames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as vscode from "vscode";
import { deserializeFlow, serializeFlow } from "@flyde/loader/dist/server";
import {
RenamePair,
rewriteRenamedFileReferences,
} from "./flyde-file-rename-utils";

export { rewriteRenamedFileReferences } from "./flyde-file-rename-utils";

export function registerFlydeFileRenameHandler(
outputChannel: vscode.OutputChannel
): vscode.Disposable {
return vscode.workspace.onDidRenameFiles(async (event) => {
const renamePairs = event.files.map((file) => ({
oldPath: file.oldUri.fsPath,
newPath: file.newUri.fsPath,
}));

const updatedFiles = await updateFlydeReferencesForRenames(renamePairs);

if (updatedFiles > 0) {
outputChannel.appendLine(
`Updated Flyde import references in ${updatedFiles} file(s).`
);
}
});
}

export async function updateFlydeReferencesForRenames(
renamePairs: RenamePair[]
): Promise<number> {
if (renamePairs.length === 0) {
return 0;
}

const flowUris = await vscode.workspace.findFiles(
"**/*.flyde",
"**/node_modules/**"
);

let updatedFiles = 0;

for (const flowUri of flowUris) {
try {
const fileBytes = await vscode.workspace.fs.readFile(flowUri);
const flowText = Buffer.from(fileBytes).toString("utf8");
const flow = deserializeFlow(flowText, flowUri.fsPath);

if (rewriteRenamedFileReferences(flow, flowUri.fsPath, renamePairs)) {
await vscode.workspace.fs.writeFile(
flowUri,
Buffer.from(serializeFlow(flow), "utf8")
);
updatedFiles += 1;
}
} catch (error) {
console.warn(
`Failed to update Flyde imports in ${flowUri.fsPath}`,
error
);
}
}

return updatedFiles;
}
67 changes: 67 additions & 0 deletions vscode/src/test/flyde-file-renames.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as path from "path";
import assert = require("assert");
import { FlydeFlow } from "@flyde/core";
import { rewriteRenamedFileReferences } from "../flyde-file-rename-utils";

suite("Flyde file rename references", () => {
test("updates relative imports when an imported flow moves", () => {
const root = path.join(path.sep, "workspace", "project");
const flowPath = path.join(root, "ParentFlow.flyde");
const flow = createFlowWithImport("ChildFlow.flyde");

const changed = rewriteRenamedFileReferences(flow, flowPath, [
{
oldPath: path.join(root, "ChildFlow.flyde"),
newPath: path.join(root, "nested", "ChildFlow.flyde"),
},
]);

assert.strictEqual(changed, true);
assert.strictEqual(getImportedPath(flow), "nested/ChildFlow.flyde");
});

test("updates imports when a folder containing imported flows moves", () => {
const root = path.join(path.sep, "workspace", "project");
const flowPath = path.join(root, "ParentFlow.flyde");
const flow = createFlowWithImport("nodes/ChildFlow.flyde");

const changed = rewriteRenamedFileReferences(flow, flowPath, [
{
oldPath: path.join(root, "nodes"),
newPath: path.join(root, "shared", "nodes"),
},
]);

assert.strictEqual(changed, true);
assert.strictEqual(getImportedPath(flow), "shared/nodes/ChildFlow.flyde");
});
});

function createFlowWithImport(importPath: string): FlydeFlow {
return {
node: {
id: "ParentFlow",
inputs: {},
outputs: {},
inputsPosition: {},
outputsPosition: {},
instances: [
{
id: "child",
nodeId: "ChildFlow",
type: "visual",
source: { type: "file", data: importPath },
inputConfig: {},
pos: { x: 0, y: 0 },
},
],
connections: [],
},
};
}

function getImportedPath(flow: FlydeFlow): string {
const source = flow.node.instances[0].source;
assert(source.type === "file");
return source.data;
}