Skip to content

Commit

Permalink
Merge pull request #75 from EDAcation/feat/project-improvements
Browse files Browse the repository at this point in the history
Project improvements
  • Loading branch information
malmeloo authored Jun 12, 2024
2 parents cfdb147 + ed155a7 commit b2e5d27
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 108 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

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

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,17 @@
},
{
"command": "edacation.removeInputFile",
"when": "view == edacation-inputFiles",
"when": "view == edacation-inputFiles && viewItem == file",
"group": "inline"
},
{
"command": "edacation.removeOutputFile",
"when": "view == edacation-outputFiles",
"when": "view == edacation-outputFiles && viewItem == file",
"group": "inline"
},
{
"command": "edacation.trashOutputFile",
"when": "view == edacation-outputFiles",
"when": "view == edacation-outputFiles && viewItem == file",
"group": "inline"
}
]
Expand Down Expand Up @@ -356,7 +356,7 @@
"@vscode/codicons": "^0.0.35",
"@vscode/webview-ui-toolkit": "^1.3.1",
"digitaljs": "github:EDAcation/digitaljs#next",
"edacation": "^0.3.6",
"edacation": "github:edacation/edacation#feat/advanced-output-files",
"nextpnr-viewer": "^0.6.1",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",
Expand Down
21 changes: 16 additions & 5 deletions src/extension/commands/files.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode';

import type {Project, ProjectFile} from '../projects/index.js';
import {OutputFileTreeItem} from '../trees/files.js';
import {FILE_FILTERS_HDL} from '../util.js';

import {CurrentProjectCommand} from './base.js';
Expand Down Expand Up @@ -68,8 +69,13 @@ export class RemoveOutputFileCommand extends CurrentProjectCommand {
return 'edacation.removeOutputFile';
}

async executeForCurrentProject(project: Project, file: ProjectFile) {
await project.removeOutputFiles([file.path]);
async executeForCurrentProject(project: Project, treeItem: OutputFileTreeItem) {
if (treeItem.type !== 'file') {
await vscode.window.showErrorMessage('Output file removal is not supported for this item');
return;
}

await project.removeOutputFiles([treeItem.file.path]);
}
}

Expand All @@ -78,11 +84,16 @@ export class TrashOutputFileCommand extends CurrentProjectCommand {
return 'edacation.trashOutputFile';
}

async executeForCurrentProject(project: Project, file: ProjectFile) {
await project.removeOutputFiles([file.path]);
async executeForCurrentProject(project: Project, treeItem: OutputFileTreeItem) {
if (treeItem.type !== 'file') {
await vscode.window.showErrorMessage('Output file trashing is not supported for this item');
return;
}

await project.removeOutputFiles([treeItem.file.path]);

try {
await vscode.workspace.fs.delete(file.uri);
await vscode.workspace.fs.delete(treeItem.file.uri);
} catch {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/extension/commands/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,6 @@ export class SelectProject extends BaseCommand {
}

async execute(project: Project) {
this.projects.setCurrent(project);
await this.projects.setCurrent(project);
}
}
167 changes: 132 additions & 35 deletions src/extension/projects/project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import {Project as BaseProject, DEFAULT_CONFIGURATION, type ProjectConfiguration, type ProjectState} from 'edacation';
import {
Project as BaseProject,
DEFAULT_CONFIGURATION,
type ProjectConfiguration,
type ProjectOutputFileState,
type ProjectState
} from 'edacation';
import path from 'path';
import * as vscode from 'vscode';

Expand All @@ -11,20 +17,38 @@ export interface ProjectFile {
uri: vscode.Uri;
}

interface ProjectFileData {
uri: vscode.Uri;
watcher: vscode.FileSystemWatcher;
}

type FileWatcherCallback = (uri: vscode.Uri) => void;

const getFileWatcher = (
uri: vscode.Uri,
onDidChange?: FileWatcherCallback,
onDidDelete?: FileWatcherCallback
): vscode.FileSystemWatcher => {
const watcher = vscode.workspace.createFileSystemWatcher(uri.fsPath, true, !onDidChange, !onDidDelete);
watcher.onDidChange((uri) => onDidChange && onDidChange(uri));
watcher.onDidDelete((uri) => onDidDelete && onDidDelete(uri));
return watcher;
};

export class Project extends BaseProject {
private readonly projects: Projects;
private uri: vscode.Uri;
private root: vscode.Uri;
private relativeRoot: string;
private inputFileUris: Map<string, vscode.Uri>;
private outputFileUris: Map<string, vscode.Uri>;
private inputFileInfo: Map<string, ProjectFileData>;
private outputFileInfo: Map<string, ProjectFileData>;

constructor(
projects: Projects,
uri: vscode.Uri,
name?: string,
inputFiles: string[] = [],
outputFiles: string[] = [],
outputFiles: string[] | ProjectOutputFileState[] = [],
configuration: ProjectConfiguration = DEFAULT_CONFIGURATION
) {
super(name ? name : path.basename(uri.path, '.edaproject'), inputFiles, outputFiles, configuration);
Expand All @@ -37,12 +61,21 @@ export class Project extends BaseProject {
});
this.relativeRoot = asWorkspaceRelativeFolderPath(this.root);

this.inputFileUris = new Map<string, vscode.Uri>(
inputFiles.map((file) => [file, vscode.Uri.joinPath(this.getRoot(), file)])
);
this.outputFileUris = new Map<string, vscode.Uri>(
outputFiles.map((file) => [file, vscode.Uri.joinPath(this.getRoot(), file)])
);
this.inputFileInfo = new Map();
for (const file of inputFiles) {
const uri = vscode.Uri.joinPath(this.getRoot(), file);
const watcher = getFileWatcher(uri, undefined, () => void this.removeInputFiles([file]));
this.inputFileInfo.set(file, {uri, watcher});
}

this.outputFileInfo = new Map();
for (const file of this.getOutputFiles()) {
const uri = vscode.Uri.joinPath(this.getRoot(), file.path);
const watcher = getFileWatcher(uri, undefined, () => void this.removeOutputFiles([file.path]));
this.outputFileInfo.set(file.path, {uri, watcher});
}

void this.cleanIOFiles();
}

getUri() {
Expand All @@ -53,28 +86,28 @@ export class Project extends BaseProject {
return this.uri.toString() === uri.toString();
}

getRoot() {
getRoot(): vscode.Uri {
return this.root;
}

getRelativeRoot() {
getRelativeRoot(): string {
return this.relativeRoot;
}

getInputFileUris() {
return Array.from(this.inputFileUris.entries(), ([key, value]) => ({path: key, uri: value}));
getInputFileUris(): {path: string; uri: vscode.Uri}[] {
return Array.from(this.inputFileInfo.entries(), ([key, value]) => ({path: key, uri: value.uri}));
}

getInputFileUri(inputFile: string) {
return this.inputFileUris.get(inputFile);
getInputFileUri(inputFile: string): vscode.Uri | undefined {
return this.inputFileInfo.get(inputFile)?.uri;
}

getOutputFileUris() {
return Array.from(this.outputFileUris.entries(), ([key, value]) => ({path: key, uri: value}));
getOutputFileUris(): {path: string; uri: vscode.Uri}[] {
return Array.from(this.outputFileInfo.entries(), ([key, value]) => ({path: key, uri: value.uri}));
}

getOutputFileUri(outputFile: string) {
return this.outputFileUris.get(outputFile);
getOutputFileUri(outputFile: string): vscode.Uri | undefined {
return this.outputFileInfo.get(outputFile)?.uri;
}

getTargetDirectory(targetId: string): string {
Expand All @@ -84,6 +117,31 @@ export class Project extends BaseProject {
return `./out/${this.getName()}/${target.id}/`;
}

private async cleanIOFiles() {
const brokenInputFiles: string[] = [];
for (const [file, data] of this.inputFileInfo) {
try {
await vscode.workspace.fs.stat(data.uri);
} catch {
console.warn(`Input file does not exist, removing: ${file}`);
brokenInputFiles.push(file);
}
}

const brokenOutputFiles: string[] = [];
for (const [file, data] of this.outputFileInfo) {
try {
await vscode.workspace.fs.stat(data.uri);
} catch {
console.warn(`Output file does not exist, removing: ${file}`);
brokenOutputFiles.push(file);
}
}

if (brokenInputFiles.length) await this.removeInputFiles(brokenInputFiles);
if (brokenOutputFiles.length) await this.removeOutputFiles(brokenOutputFiles);
}

async updateTargetDirectories() {
const targets = this.getConfiguration()['targets'].map((target) => ({
...target,
Expand All @@ -109,26 +167,47 @@ export class Project extends BaseProject {

if (!this.hasInputFile(folderRelativePath)) {
filePaths.push(folderRelativePath);
this.inputFileUris.set(folderRelativePath, fileUri);
this.inputFileInfo.set(folderRelativePath, {
uri: fileUri,
watcher: getFileWatcher(
fileUri,
() => void this.markOutputFilesStale(true),
() => void this.removeInputFiles([folderRelativePath || ''])
)
});
}
}

super.addInputFiles(filePaths);

this.projects.emitInputFileChange();

this.markOutputFilesStale(false);

await this.save();
}

async markOutputFilesStale(doSave = true) {
this.expireOutputFiles();
this.projects.emitOutputFileChange();

if (doSave) await this.save();
}

async removeInputFiles(filePaths: string[]): Promise<void> {
if (!filePaths.length) return;

for (const filePath of filePaths) {
this.inputFileUris.delete(filePath);
this.inputFileInfo.get(filePath)?.watcher.dispose();
this.inputFileInfo.delete(filePath);
}

super.removeInputFiles(filePaths);

this.projects.emitInputFileChange();

this.markOutputFilesStale(false);

await this.save();
}

Expand All @@ -143,31 +222,40 @@ export class Project extends BaseProject {
return;
}

const answer = await vscode.window.showErrorMessage(
`Copy file into EDA project root?`,
const answer = await vscode.window.showInformationMessage(
`Copy file into workspace?`,
{
detail: `File "${uri.path}" is not in folder "${
this.getRoot().path
}". Do you want to copy it into the project root?`,
}". Do you want to copy it into the project workspace?`,
modal: true
},
'Yes',
'No'
);
if (answer === 'Yes') {
const target = vscode.Uri.joinPath(this.getRoot(), path.basename(uri.path));

return new Promise((resolve, _reject) => {
fs.copyFile(uri.fsPath, target.fsPath, () => {
resolve(target);
const targetDir = vscode.Uri.joinPath(this.getRoot(), 'src');
const target = vscode.Uri.joinPath(targetDir, path.basename(uri.path));

return new Promise((resolve, reject) => {
fs.mkdir(targetDir.fsPath, {recursive: true}, (err) => {
if (err) {
reject();
vscode.window.showErrorMessage(`Failed to copy file: ${err}`);
return;
}

fs.copyFile(uri.fsPath, target.fsPath, () => {
resolve(target);
});
});
});
}

return;
}

async addOutputFileUris(fileUris: vscode.Uri[]): Promise<void> {
async addOutputFileUris(fileUris: vscode.Uri[], targetId: string): Promise<void> {
const filePaths = [];
for (let fileUri of fileUris) {
// eslint-disable-next-line prefer-const
Expand All @@ -181,13 +269,21 @@ export class Project extends BaseProject {
}
if (!folderRelativePath) continue;

filePaths.push(folderRelativePath);

if (!this.hasOutputFile(folderRelativePath)) {
filePaths.push(folderRelativePath);
this.outputFileUris.set(folderRelativePath, fileUri);
this.outputFileInfo.set(folderRelativePath, {
uri: fileUri,
watcher: getFileWatcher(
fileUri,
undefined,
() => void this.removeOutputFiles([folderRelativePath || ''])
)
});
}
}

super.addOutputFiles(filePaths);
super.addOutputFiles(filePaths.map((path) => ({path, targetId})));

this.projects.emitOutputFileChange();

Expand All @@ -196,7 +292,8 @@ export class Project extends BaseProject {

async removeOutputFiles(filePaths: string[]): Promise<void> {
for (const filePath of filePaths) {
this.outputFileUris.delete(filePath);
this.outputFileInfo.get(filePath)?.watcher.dispose();
this.outputFileInfo.delete(filePath);
}

super.removeOutputFiles(filePaths);
Expand Down
Loading

0 comments on commit b2e5d27

Please sign in to comment.