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
74 changes: 74 additions & 0 deletions vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";

import { deserializeFlow, serializeFlow } from "@flyde/loader/dist/server";
import { updateMovedFileReferences } from "./updateMovedFileReferences";
import { FlydeEditorEditorProvider } from "./flydeEditor";
import * as execa from "execa";

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

// Update Flyde references before VS Code moves or renames files.
context.subscriptions.push(
vscode.workspace.onWillRenameFiles((event) => {
event.waitUntil(
(async () => {
const edit = new vscode.WorkspaceEdit();

const moves = event.files.map(({ oldUri, newUri }) => ({
oldPath: oldUri.fsPath,
newPath: newUri.fsPath,
}));

// Search for Flyde flows in the workspace.
const flowFiles = await vscode.workspace.findFiles(
"**/*.flyde",
"**/node_modules/**"
);

for (const flowUri of flowFiles) {
try {
// Read the current document, including unsaved changes.
const document =
await vscode.workspace.openTextDocument(flowUri);

const originalText = document.getText();

if (!originalText.trim()) {
continue;
}

const flow = deserializeFlow(
originalText,
flowUri.fsPath
);

// Recalculate file-backed references.
const changed = updateMovedFileReferences(
flow,
flowUri.fsPath,
moves
);

if (!changed) {
continue;
}

const updatedText = serializeFlow(flow);

const entireDocument = new vscode.Range(
document.positionAt(0),
document.positionAt(originalText.length)
);

edit.replace(
flowUri,
entireDocument,
updatedText
);

} catch (error) {
mainOutputChannel.appendLine(
`Could not update references in ${flowUri.fsPath}: ${String(error)}`
);
}
}

return edit;
})()
);
})
);

let currentTheme = vscode.window.activeColorTheme;

let useDarkMode =
Expand Down
122 changes: 122 additions & 0 deletions vscode/src/updateMovedFileReferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@

import * as path from "path";
import { FlydeFlow, VisualNode } from "@flyde/core";

export interface FileMove {
oldPath: string;
newPath: string;
}

/**
* Resolve a file's new location after one or more file/folder moves.
*/
function getMovedPath(
filePath: string,
moves: FileMove[]
): string {
const absolutePath = path.resolve(filePath);

for (const move of moves) {
const oldPath = path.resolve(move.oldPath);
const newPath = path.resolve(move.newPath);

const relative = path.relative(oldPath, absolutePath);

// Match the exact file or a file inside a moved folder.
if (
relative === "" ||
(relative !== ".." &&
!relative.startsWith(".." + path.sep) &&
!path.isAbsolute(relative))
) {
return path.resolve(newPath, relative);
}
}

return absolutePath;
}

/**
* Update file-backed node references inside a Flyde flow.
*
* Returns true when at least one reference changed.
*/
export function updateMovedFileReferences(
flow: FlydeFlow,
flowFilePath: string,
moves: FileMove[]
): boolean {
let changed = false;

const oldFlowPath = path.resolve(flowFilePath);
const newFlowPath = getMovedPath(oldFlowPath, moves);

function updateNode(node: VisualNode): void {
for (const instance of node.instances) {
const source = instance.source;

if (!source) {
continue;
}

if (
source.type === "file" &&
typeof source.data === "string"
) {
const wasAbsolute = path.isAbsolute(source.data);

const oldTarget = path.resolve(
path.dirname(oldFlowPath),
source.data
);

const newTarget = getMovedPath(oldTarget, moves);

if (
oldTarget !== newTarget ||
oldFlowPath !== newFlowPath
) {
let updatedReference: string;

if (wasAbsolute) {
updatedReference = newTarget;
} else {
updatedReference = path.relative(
path.dirname(newFlowPath),
newTarget
);

// Keep relative imports explicitly relative.
if (
!updatedReference.startsWith(".")
) {
updatedReference = "./" + updatedReference;
}

// Flyde references use forward slashes.
updatedReference =
updatedReference.replace(/\\/g, "/");
}

if (source.data !== updatedReference) {
source.data = updatedReference;
changed = true;
}
}
}

// Also handle nested inline visual nodes.
if (
source.type === "inline" &&
source.data &&
typeof source.data === "object"
) {
updateNode(source.data as VisualNode);
}
}
}

updateNode(flow.node);

return changed;
}