diff --git a/.eslintrc.json b/.eslintrc.json index f9b22b7..5dfecab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,24 +1,18 @@ { - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/naming-convention": "warn", - "@typescript-eslint/semi": "warn", - "curly": "warn", - "eqeqeq": "warn", - "no-throw-literal": "warn", - "semi": "off" - }, - "ignorePatterns": [ - "out", - "dist", - "**/*.d.ts" - ] + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/naming-convention": "warn", + "@typescript-eslint/semi": "warn", + "curly": "warn", + "eqeqeq": "warn", + "no-throw-literal": "warn", + "semi": "off" + }, + "ignorePatterns": ["out", "dist", "**/*.d.ts"] } diff --git a/.gitignore b/.gitignore index 6704566..43caf2e 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,9 @@ dist # TernJS port file .tern-port + +#test +.vscode-test + +#typescript compiled artifacts +out/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3ac9aeb..c0a2258 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,5 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint"] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 6e84338..53aeb05 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,34 +3,30 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--disable-extensions", - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "args": [ - "--disable-extensions", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--disable-extensions", + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--disable-extensions", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": ["${workspaceFolder}/out/test/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 30bf8c2..e375ad4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,12 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" -} \ No newline at end of file + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off", + "editor.formatOnSave": true +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..078ff7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 866cea4..2470d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [1.0.0] - 2023-03-12 + +## [1.1.0] - 2023-04-11 + +### Added + +- Mark/Unmark multiple files from the file explorer (Thanks @UsmannK) +- Switch to '.gitignore format' for the `scope.txt` file (Thanks @DavidBDiligence) +- Switch from disk-based storage to workspace memory (for future web compatibility) +- Add a feature to export marked files to a custom file on disk + +### Changed + +- Formatted files using Prettier. diff --git a/LICENSE.md b/LICENSE.md index 418db4a..16dd154 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 33bdbe6..ad8af5b 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ The extension allows you to mark/unmark files by: -* Providing a `scope.txt` file in the workspace root folder. The file contains a list of absolute file paths or relative file paths to the workspace root folder. The file paths must be separated by newlines. -* Right clicking on a file in the file explorer and selecting `Mark/Unmark File` from the context menu. -* Right clicking on an editor tab and selecting `Mark/Unmark File` from the context menu. +- Providing a `scope.txt` file in the workspace root folder. The file contains a list of patterns for files to include in the scope. The patterns are written using the [.gitignore format](https://git-scm.com/docs/gitignore#_pattern_format), e.g., `**.sol` to include all Solidity files in all directories. +- Selecting a single/multiple files in the explorer and clicking on `Mark/Unmark File` from the context menu. +- Right clicking on an editor tab and selecting `Mark/Unmark File` from the context menu. -Note that the marked files are stored in a `scope.txt` file in the workspace root folder. If the file does not exist, it will be created. +Note that the marked files are stored in the workspace local storage. In case of mulit-root workspaces, if you don't save the workspace, the marks will be discarded if the workspace is closed. You can also export the marked files to a file on disk by invoking the `Export maked files to a file` command. For multi-root workspaces, marked files will be exported to individual files located at the root of each opened foler. Marked files exported to disk can be re-imported by naming the export file as `scope.txt`, and using the import feature. ### Mark/Unmark files from contextual menus @@ -20,13 +20,12 @@ Note that the marked files are stored in a `scope.txt` file in the workspace roo ![Mark files from scope file](images/scope.gif) - ## Extension Settings This extension contributes the following settings: -* `markfiles.colorMarkedFile`: Choose whether to mark files by adding an icon, changing the color of the file name or both. -* `markfiles.markedFileIcon`: Choose the symbol to use for marked files. Note that the symbol must be a single unicode character. +- `markfiles.colorMarkedFile`: Choose whether to mark files by adding an icon, changing the color of the file name or both. +- `markfiles.markedFileIcon`: Choose the symbol to use for marked files. Note that the symbol must be a single unicode character. The color used to mark files can be changed by modifying the following key in your `settings.json` file: @@ -36,7 +35,6 @@ The color used to mark files can be changed by modifying the following key in yo } ``` - ## Release Notes Check [CHANGELOG.md](CHANGELOG.md) diff --git a/out/extension.js b/out/extension.js deleted file mode 100644 index 30eea00..0000000 --- a/out/extension.js +++ /dev/null @@ -1,46 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.deactivate = exports.activate = void 0; -const vscode = require("vscode"); -const fileDecorationProvider_1 = require("./fileDecorationProvider"); -var provider; -// This method is called when the extension is activated -function activate(context) { - //register the decoration provider - provider = new fileDecorationProvider_1.DecorationProvider(); - let disposable = vscode.window.registerFileDecorationProvider(provider); - context.subscriptions.push(disposable); - disposable = vscode.commands.registerCommand('markfiles.markUnmarkFile', async (contextUri) => { - const uri = contextUri || vscode.window.activeTextEditor?.document.uri; - if (uri) { - const stat = await vscode.workspace.fs.stat(uri); - if (stat.type !== vscode.FileType.File) { - return; - } //can't mark directory - if (provider.markedFiles.has(uri.fsPath)) { - provider.update([], [uri.fsPath]); - } - else { - provider.update([uri.fsPath], []); - } - } - }); - context.subscriptions.push(disposable); - disposable = vscode.commands.registerCommand('markfiles.reloadFromScopeFile', async () => { - provider.loadFromScopeFile(true); - vscode.window.showInformationMessage('Loading marked files from scope file(s)'); - }); - context.subscriptions.push(disposable); - //listen to configuration changes - vscode.workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('markfiles')) { - provider.configChanged(); - } - }); - context.subscriptions.push(disposable); -} -exports.activate = activate; -// This method is called when the extension is deactivated -function deactivate() { } -exports.deactivate = deactivate; -//# sourceMappingURL=extension.js.map \ No newline at end of file diff --git a/out/extension.js.map b/out/extension.js.map deleted file mode 100644 index 048c829..0000000 --- a/out/extension.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":";;;AAAA,iCAAiC;AACjC,qEAA8D;AAE9D,IAAI,QAA4B,CAAC;AAEjC,wDAAwD;AACxD,SAAgB,QAAQ,CAAC,OAAgC;IAExD,kCAAkC;IAClC,QAAQ,GAAG,IAAI,2CAAkB,EAAE,CAAC;IACpC,IAAI,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,8BAA8B,CAAC,QAAQ,CAAC,CAAC;IACxE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEvC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,0BAA0B,EAAE,KAAK,EAAE,UAAsB,EAAE,EAAE;QACzG,MAAM,GAAG,GAAG,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GAAG,CAAC;QACvE,IAAI,GAAG,EAAE;YACR,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;gBAAC,OAAO;aAAC,CAAC,sBAAsB;YACxE,IAAI,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;gBACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;aAClC;iBAAM;gBACN,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;aAClC;SACD;IACF,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEvC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QACxF,QAAQ,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,yCAAyC,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEvC,iCAAiC;IACjC,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/C,IAAI,CAAC,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE;YACxC,QAAQ,CAAC,aAAa,EAAE,CAAC;SACzB;IAAA,CAAC,CAAC,CAAC;IACL,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAGxC,CAAC;AAnCD,4BAmCC;AAED,0DAA0D;AAC1D,SAAgB,UAAU,KAAI,CAAC;AAA/B,gCAA+B"} \ No newline at end of file diff --git a/out/fileDecorationProvider.js b/out/fileDecorationProvider.js deleted file mode 100644 index a6c35d5..0000000 --- a/out/fileDecorationProvider.js +++ /dev/null @@ -1,114 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DecorationProvider = void 0; -const path = require("path"); -const vscode_1 = require("vscode"); -const utils_1 = require("./utils"); -const fs_1 = require("fs"); -class DecorationProvider { - constructor() { - this._onDidChangeFileDecorations = new vscode_1.EventEmitter(); - this.onDidChangeFileDecorations = this._onDidChangeFileDecorations.event; - this.markedFiles = new Set(); - this.scopeFilesByProjetRootsURIs = {}; //project root URI --> scope file URIs - this.loadFromScopeFile(); - } - provideFileDecoration(uri, token) { - if (token.isCancellationRequested) { - return {}; - } - const config = vscode_1.workspace.getConfiguration('markfiles'); - const colorize = config.colorMarkedFile === 'color' || config.colorMarkedFile === 'both'; - const icon = config.colorMarkedFile === 'icon' || config.colorMarkedFile === 'both'; - if (this.markedFiles.has(uri.fsPath)) { - return { - propagate: true, - badge: icon && config.markedFileIcon, - tooltip: 'This file is marked', - color: colorize && new vscode_1.ThemeColor('markfiles.markedFileColor'), - }; - } - return {}; - } - //mark or unmark files - async update(markedFiles, unmarkedFiles) { - if (!markedFiles.length && !unmarkedFiles.length) { - return; - } - markedFiles.forEach((markedFile) => { - if (!this.markedFiles.has(markedFile)) { - this.markedFiles.add(markedFile); - this._onDidChangeFileDecorations.fire(vscode_1.Uri.file(markedFile)); - } - }); - unmarkedFiles.forEach((unmarkedFile) => { - if (this.markedFiles.delete(unmarkedFile)) { - this._onDidChangeFileDecorations.fire(vscode_1.Uri.file(unmarkedFile)); - } - }); - this.writeMarkedFilesToFile(); - } - async loadFromScopeFile(reload = false) { - if (reload) { - this.markedFiles.forEach((markedFile) => { - this.markedFiles.delete(markedFile); - this._onDidChangeFileDecorations.fire(vscode_1.Uri.file(markedFile)); - }); - } - const rootFolders = vscode_1.workspace.workspaceFolders?.map((folder) => folder.uri.path); - if (!rootFolders) { - return; - } - for (const rf of rootFolders) { - const scopeFilePath = path.join(rf, 'scope.txt'); - this.scopeFilesByProjetRootsURIs[rf] = scopeFilePath; - } - for (const wsURI in this.scopeFilesByProjetRootsURIs) { - const scopeFileURI = this.scopeFilesByProjetRootsURIs[wsURI]; - if ((0, fs_1.existsSync)(scopeFileURI)) { - this.loadMarkedFiles(scopeFileURI, wsURI); //load marked files from `scope` file in workspace root - } - } - } - async configChanged() { - this.markedFiles.forEach((markedFile) => this._onDidChangeFileDecorations.fire(vscode_1.Uri.file(markedFile))); - } - async loadMarkedFiles(scopeUri, projectRootUri) { - let markedRelPath = (await (0, utils_1.asyncReadFile)(scopeUri)) || []; - let markedAbsPath = markedRelPath - .filter((p) => p.length > 0) - .map((relPath) => path.resolve(projectRootUri, relPath)); - //.filter(async (p) => await (await workspace.fs.stat(Uri.parse(p))).type === FileType.File) - this.update(markedAbsPath, []); - } - //write marked files to `scope` file in workspace root - async writeMarkedFilesToFile() { - if (!Object.keys(this.scopeFilesByProjetRootsURIs).length) { - return; - } - // sort by workspace folders - const markedFilesByWS = Array.from(this.markedFiles).reduce((acc, uri) => { - const workspaceFolder = vscode_1.workspace.getWorkspaceFolder(vscode_1.Uri.parse(uri)); - if (workspaceFolder) { - if (!acc[workspaceFolder.uri.fsPath]) { - acc[workspaceFolder.uri.fsPath] = [uri]; - } - else { - acc[workspaceFolder.uri.fsPath].push(uri); - } - } - return acc; - }, {}); - for (const workspaceUri in markedFilesByWS) { - const markedFiles = markedFilesByWS[workspaceUri].join('\n'); - const path = this.scopeFilesByProjetRootsURIs[workspaceUri]; - (0, fs_1.writeFile)(path, markedFiles, { flag: 'w' }, (err) => { - if (err) { - console.error(`markfiles: Failed to write scope file with path ${path}. Err: ${err}`); - } - }); - } - } -} -exports.DecorationProvider = DecorationProvider; -//# sourceMappingURL=fileDecorationProvider.js.map \ No newline at end of file diff --git a/out/fileDecorationProvider.js.map b/out/fileDecorationProvider.js.map deleted file mode 100644 index 2f8ac1f..0000000 --- a/out/fileDecorationProvider.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fileDecorationProvider.js","sourceRoot":"","sources":["../src/fileDecorationProvider.ts"],"names":[],"mappings":";;;AAAA,6BAA8B;AAC9B,mCAUgB;AAChB,mCAAwC;AACxC,2BAA2C;AAE3C,MAAa,kBAAkB;IAQ7B;QAPiB,gCAA2B,GAC1C,IAAI,qBAAY,EAAe,CAAC;QACzB,+BAA0B,GACjC,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC;QAClC,gBAAW,GAAgB,IAAI,GAAG,EAAU,CAAC;QAC5C,gCAA2B,GAAmC,EAAE,CAAC,CAAC,sCAAsC;QAG9G,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAED,qBAAqB,CAAC,GAAQ,EAAE,KAAwB;QACtD,IAAI,KAAK,CAAC,uBAAuB,EAAE;YACjC,OAAO,EAAE,CAAC;SACX;QACD,MAAM,MAAM,GAAG,kBAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACvD,MAAM,QAAQ,GACZ,MAAM,CAAC,eAAe,KAAK,OAAO,IAAI,MAAM,CAAC,eAAe,KAAK,MAAM,CAAC;QAC1E,MAAM,IAAI,GACR,MAAM,CAAC,eAAe,KAAK,MAAM,IAAI,MAAM,CAAC,eAAe,KAAK,MAAM,CAAC;QACzE,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;YACpC,OAAO;gBACL,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE,IAAI,IAAI,MAAM,CAAC,cAAc;gBACpC,OAAO,EAAE,qBAAqB;gBAC9B,KAAK,EAAE,QAAQ,IAAI,IAAI,mBAAU,CAAC,2BAA2B,CAAC;aAC/D,CAAC;SACH;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,sBAAsB;IACf,KAAK,CAAC,MAAM,CACjB,WAA0B,EAC1B,aAA4B;QAE5B,IAAI,CAAC,WAAW,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE;YAChD,OAAO;SACR;QACD,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE;YACjC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;gBACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACjC,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,YAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;aAC7D;QACH,CAAC,CAAC,CAAC;QACH,aAAa,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE;YACrC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE;gBACzC,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,YAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;aAC/D;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,sBAAsB,EAAE,CAAC;IAChC,CAAC;IAEM,KAAK,CAAC,iBAAiB,CAAC,SAAkB,KAAK;QACpD,IAAI,MAAM,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE;gBACtC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBACpC,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,YAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAC9D,CAAC,CAAC,CAAC;SACJ;QACD,MAAM,WAAW,GAAG,kBAAS,CAAC,gBAAgB,EAAE,GAAG,CACjD,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC5B,CAAC;QACF,IAAI,CAAC,WAAW,EAAE;YAChB,OAAO;SACR;QACD,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE;YAC5B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;YACjD,IAAI,CAAC,2BAA2B,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC;SACtD;QAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,2BAA2B,EAAE;YACpD,MAAM,YAAY,GAAG,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,CAAC;YAC7D,IAAI,IAAA,eAAU,EAAC,YAAY,CAAC,EAAE;gBAC5B,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,uDAAuD;aACnG;SACF;IACH,CAAC;IAEM,KAAK,CAAC,aAAa;QACxB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CACtC,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,YAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAC5D,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,QAAgB,EAAE,cAAsB;QAC5D,IAAI,aAAa,GAAG,CAAC,MAAM,IAAA,qBAAa,EAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,IAAI,aAAa,GAAG,aAAa;aAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;aAC3B,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3D,4FAA4F;QAC5F,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,sDAAsD;IAC9C,KAAK,CAAC,sBAAsB;QAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE;YACzD,OAAO;SACR;QACD,4BAA4B;QAC5B,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAExD,CAAC,GAAgC,EAAE,GAAW,EAAE,EAAE;YACnD,MAAM,eAAe,GAAG,kBAAS,CAAC,kBAAkB,CAAC,YAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACrE,IAAI,eAAe,EAAE;gBACnB,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;oBACpC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;iBACzC;qBAAM;oBACL,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;iBAC3C;aACF;YACD,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,KAAK,MAAM,YAAY,IAAI,eAAe,EAAE;YAC1C,MAAM,WAAW,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,2BAA2B,CAAC,YAAY,CAAC,CAAC;YAC5D,IAAA,cAAS,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;gBAClD,IAAI,GAAG,EAAE;oBACP,OAAO,CAAC,KAAK,CACX,mDAAmD,IAAI,UAAU,GAAG,EAAE,CACvE,CAAC;iBACH;YACH,CAAC,CAAC,CAAC;SACJ;IACH,CAAC;CACF;AA/HD,gDA+HC"} \ No newline at end of file diff --git a/out/utils.js b/out/utils.js deleted file mode 100644 index 9ce67bf..0000000 --- a/out/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.asyncReadFile = void 0; -const fs_1 = require("fs"); -async function asyncReadFile(path) { - try { - const contents = await fs_1.promises.readFile(path, 'utf-8'); - const arr = contents.split(/\r?\n/); - return arr; - } - catch (err) { - console.error(`markfiles: Failed to read file with path ${path} from disk. err: ${err}`); - } -} -exports.asyncReadFile = asyncReadFile; -//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/out/utils.js.map b/out/utils.js.map deleted file mode 100644 index 3377aa0..0000000 --- a/out/utils.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,2BAA0C;AAEnC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,IAAI;QACF,MAAM,QAAQ,GAAG,MAAM,aAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEpC,OAAO,GAAG,CAAC;KACZ;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,KAAK,CAAC,4CAA4C,IAAI,oBAAoB,GAAG,EAAE,CAAC,CAAC;KAC1F;AACH,CAAC;AAVD,sCAUC"} \ No newline at end of file diff --git a/package.json b/package.json index f3307b2..73b0ea6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "markfiles", "displayName": "Mark Files", "description": "An extension that allows you to mark files in the file explorer", - "version": "1.0.0", + "version": "1.1.0", "publisher": "vquelque", "engines": { "vscode": "^1.76.0" @@ -14,7 +14,9 @@ "Other" ], "icon": "icon.png", - "activationEvents": ["onStartupFinished"], + "activationEvents": [ + "onStartupFinished" + ], "main": "./out/extension.js", "contributes": { "commands": [ @@ -29,18 +31,26 @@ { "command": "markfiles.reloadFromScopeFile", "title": "Reload marked files from scope file" + }, + { + "command": "markfiles.writeMarkedFilesToDisk", + "title": "Export marked files to a file" } - ], + ], "menus": { - "explorer/context": [{ - "command": "markfiles.markUnmarkSelectedFile", - "group": "Mark/Unmark file" - }], - "editor/title/context": [{ - "command": "markfiles.markUnmarkActiveFile", - "group": "Mark/Unmark file" - }] - }, + "explorer/context": [ + { + "command": "markfiles.markUnmarkSelectedFile", + "group": "Mark/Unmark file" + } + ], + "editor/title/context": [ + { + "command": "markfiles.markUnmarkActiveFile", + "group": "Mark/Unmark file" + } + ] + }, "colors": [ { "id": "markfiles.markedFileColor", @@ -58,7 +68,11 @@ "type": "string", "markdownDescription": "Choose whether to mark files by adding an icon, changing the color of the file name or both. Customize the color of the filename in the `#workbench.colorCustomizations#` section using the `markfiles.markedFileColor` key", "default": "icon", - "enum": ["icon", "color", "both"], + "enum": [ + "icon", + "color", + "both" + ], "enumDescriptions": [ "Mark files by adding an icon", "Mark files by changing the color of the file name", @@ -70,8 +84,12 @@ "description": "Choose the symbol to use for marked files (only unicode character)", "default": "📌", "maxLength": 2, - "minLength":1, - "pattern": "[^\\x00-\\x7F]+" + "minLength": 1, + "pattern": "[^\\x00-\\x7F]+" + }, + "markfiles.autoloadFromScope": { + "type": "boolean", + "description": "Specifies whether to automatically try loading marked files from the scope file in case no files are marked in the current workspace" } } } @@ -80,26 +98,30 @@ "vscode:prepublish": "yarn run compile", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", - "pretest": "yarn run compile && yarn run lint", + "copy-files": "cp -r ./src/test/example ./out/test", + "pretest": "yarn run compile && yarn run lint && yarn run copy-files", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js" }, "devDependencies": { - "@types/vscode": "^1.76.0", "@types/glob": "^8.1.0", "@types/mocha": "^10.0.1", "@types/node": "16.x", + "@types/vscode": "^1.76.0", "@typescript-eslint/eslint-plugin": "^5.53.0", "@typescript-eslint/parser": "^5.53.0", + "@vscode/test-electron": "^2.2.3", "eslint": "^8.34.0", "glob": "^8.1.0", "mocha": "^10.2.0", - "typescript": "^4.9.5", - "@vscode/test-electron": "^2.2.3" + "typescript": "^4.9.5" }, "repository": { "type": "git", "url": "https://github.com/vquelque/vscode_mark_files.git" }, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ignore": "^5.2.4" + } } diff --git a/src/extension.ts b/src/extension.ts index 486f59e..e43de1b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,63 +1,104 @@ -import * as vscode from 'vscode'; -import { DecorationProvider } from './fileDecorationProvider'; +import * as vscode from "vscode"; +import { DecorationProvider } from "./fileDecorationProvider"; -var provider: DecorationProvider; +let provider: DecorationProvider; +let outputChannel: vscode.OutputChannel; +let storage: vscode.Memento; // This method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { + //create WS storage + storage = context.workspaceState; + if (!storage) { + vscode.window.showInformationMessage( + "Please create a workspace to save the marked files" + ); + } + //initialize output channel + outputChannel = vscode.window.createOutputChannel("Mark Files"); - //register the decoration provider - provider = new DecorationProvider(); - let disposable = vscode.window.registerFileDecorationProvider(provider); - context.subscriptions.push(disposable); - - context.subscriptions.push( - vscode.commands.registerCommand('markfiles.markUnmarkActiveFile', async (contextUri: vscode.Uri) => { - const uri = vscode.window.activeTextEditor?.document.uri; - if (uri) { - const stat = await vscode.workspace.fs.stat(uri); - if (stat.type !== vscode.FileType.File) {return;} //can't mark directory - if (provider.markedFiles.has(uri.fsPath)) { - provider.update([], [uri.fsPath]); - } else { - provider.update([uri.fsPath], []); - } - } - }) - ); - - context.subscriptions.push( - vscode.commands.registerCommand('markfiles.markUnmarkSelectedFile', async (clickedFile: vscode.Uri, selectedFiles: vscode.Uri[]) => { - for (const uri of selectedFiles) { - if (uri) { - const stat = await vscode.workspace.fs.stat(uri); - if (stat.type !== vscode.FileType.File) {continue;} //can't mark directory - if (provider.markedFiles.has(uri.fsPath)) { - provider.update([], [uri.fsPath]); - } else { - provider.update([uri.fsPath], []); - } - } - } - }) - ); - - context.subscriptions.push( - vscode.commands.registerCommand('markfiles.reloadFromScopeFile', async () => { - provider.loadFromScopeFile(true); - vscode.window.showInformationMessage('Loading marked files from scope file(s)'); - }) - ); - - //listen to configuration changes - vscode.workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('markfiles')) { - provider.configChanged(); - }}); - context.subscriptions.push(disposable); - - + //register the decoration provider + provider = new DecorationProvider(); + context.subscriptions.push( + vscode.window.registerFileDecorationProvider(provider) + ); + + context.subscriptions.push( + vscode.commands.registerCommand( + "markfiles.markUnmarkActiveFile", + async (contextUri: vscode.Uri) => { + const uri = vscode.window.activeTextEditor?.document.uri; + if (uri) { + provider.markOrUnmarkFiles([uri]); + } + } + ) + ); + + context.subscriptions.push( + vscode.commands.registerCommand( + "markfiles.writeMarkedFilesToDisk", + async () => { + await provider.exportMarkedFilesToFile(); + } + ) + ); + + context.subscriptions.push( + vscode.commands.registerCommand( + "markfiles.markUnmarkSelectedFile", + async (clickedFile: vscode.Uri, selectedFiles: vscode.Uri[]) => { + provider.markOrUnmarkFiles(selectedFiles); + } + ) + ); + + context.subscriptions.push( + vscode.commands.registerCommand( + "markfiles.reloadFromScopeFile", + async () => { + provider.loadFromScopeFile(true); + vscode.window.showInformationMessage( + "Loading marked files from scope file(s)" + ); + } + ) + ); + + context.subscriptions.push( + //listen to configuration changes + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration("markfiles")) { + provider.configChanged(); + } + }) + ); + + context.subscriptions.push( + vscode.workspace.onDidRenameFiles(async (rename) => { + if (rename.files.length === 0) { + return; + } + + for (const file of rename.files) { + provider.handleFileRename(file.oldUri, file.newUri); + } + }) + ); + return context; } +//print to the output channel +export const printChannelOutput = (content: string, reveal = false): void => { + outputChannel.appendLine(content); + if (reveal) { + outputChannel.show(true); + } +}; + +export const getStorage = () => { + return storage; +}; + // This method is called when the extension is deactivated -export function deactivate() {} \ No newline at end of file +export function deactivate() {} diff --git a/src/fileDecorationProvider.ts b/src/fileDecorationProvider.ts index ab63d20..17cdf17 100644 --- a/src/fileDecorationProvider.ts +++ b/src/fileDecorationProvider.ts @@ -1,4 +1,4 @@ -import path = require('path'); +import path = require("path"); import { Uri, CancellationToken, @@ -9,9 +9,12 @@ import { workspace, ThemeColor, FileType, -} from 'vscode'; -import { asyncReadFile } from './utils'; -import { existsSync, writeFile } from 'fs'; + window, +} from "vscode"; +import { asyncReadFile } from "./utils"; +import { existsSync, writeFile } from "fs"; +import ignore from "ignore"; +import { getStorage, printChannelOutput } from "./extension"; export class DecorationProvider implements FileDecorationProvider { private readonly _onDidChangeFileDecorations: EventEmitter = @@ -19,74 +22,74 @@ export class DecorationProvider implements FileDecorationProvider { readonly onDidChangeFileDecorations: Event = this._onDidChangeFileDecorations.event; public markedFiles: Set = new Set(); - private scopeFilesByProjetRootsURIs: { [scopeUri: string]: string } = {}; //project root URI --> scope file URIs constructor() { - this.loadFromScopeFile(); + //try loading the existing marks + if (!this.loadMarkedFilesFromWSStorage()) { + //fallback to scope file if user config allows + const config = workspace.getConfiguration("markfiles"); + config.autoloadFromScope && this.loadFromScopeFile(); + } } provideFileDecoration(uri: Uri, token: CancellationToken): FileDecoration { if (token.isCancellationRequested) { return {}; } - const config = workspace.getConfiguration('markfiles'); + const config = workspace.getConfiguration("markfiles"); const colorize = - config.colorMarkedFile === 'color' || config.colorMarkedFile === 'both'; + config.colorMarkedFile === "color" || config.colorMarkedFile === "both"; const icon = - config.colorMarkedFile === 'icon' || config.colorMarkedFile === 'both'; + config.colorMarkedFile === "icon" || config.colorMarkedFile === "both"; if (this.markedFiles.has(uri.fsPath)) { return { propagate: true, badge: icon && config.markedFileIcon, - tooltip: 'This file is marked', - color: colorize && new ThemeColor('markfiles.markedFileColor'), + tooltip: "This file is marked", + color: colorize && new ThemeColor("markfiles.markedFileColor"), }; } return {}; } //mark or unmark files - public async update( - markedFiles: Array, - unmarkedFiles: Array - ) { - if (!markedFiles.length && !unmarkedFiles.length) { - return; - } - markedFiles.forEach((markedFile) => { - if (!this.markedFiles.has(markedFile)) { - this.markedFiles.add(markedFile); - this._onDidChangeFileDecorations.fire(Uri.file(markedFile)); + public async markOrUnmarkFiles(uris: Uri[]) { + uris.forEach(async (uri) => { + const stat = await workspace.fs.stat(uri); + if (stat.type !== FileType.File) { + console.log("can't mark directory"); + return; //can't mark directory } - }); - unmarkedFiles.forEach((unmarkedFile) => { - if (this.markedFiles.delete(unmarkedFile)) { - this._onDidChangeFileDecorations.fire(Uri.file(unmarkedFile)); + const fPath = uri.fsPath; + if (this.markedFiles.has(uri.fsPath)) { + this.markedFiles.delete(fPath); + getStorage().update(fPath, undefined); //remove from WS storage + } else { + this.markedFiles.add(fPath); + getStorage().update(fPath, true); // add to WS storage } + this._onDidChangeFileDecorations.fire(uri); }); - this.writeMarkedFilesToFile(); } + //reload marked files by reading the scope file + //this clears exising marks public async loadFromScopeFile(reload: boolean = false) { + const scopeFileUrisByFolder = this.getFileURIsByFolderForWS("scope", "txt"); if (reload) { - this.markedFiles.forEach((markedFile) => { - this.markedFiles.delete(markedFile); - this._onDidChangeFileDecorations.fire(Uri.file(markedFile)); - }); - } - const rootFolders = workspace.workspaceFolders?.map( - (folder) => folder.uri.path - ); - if (!rootFolders) { - return; - } - for (const rf of rootFolders) { - const scopeFilePath = path.join(rf, 'scope.txt'); - this.scopeFilesByProjetRootsURIs[rf] = scopeFilePath; + const confirm = await window.showInformationMessage( + "This operation will clear all marked files. Do you want to continue?", + "Yes", + "No" + ); + if (confirm === "No") { + //abort + return; + } + this.clearMarkedFilesForWS(); } - - for (const wsURI in this.scopeFilesByProjetRootsURIs) { - const scopeFileURI = this.scopeFilesByProjetRootsURIs[wsURI]; + for (const wsURI in scopeFileUrisByFolder) { + const scopeFileURI = scopeFileUrisByFolder[wsURI]; if (existsSync(scopeFileURI)) { this.loadMarkedFiles(scopeFileURI, wsURI); //load marked files from `scope` file in workspace root } @@ -100,17 +103,35 @@ export class DecorationProvider implements FileDecorationProvider { } async loadMarkedFiles(scopeUri: string, projectRootUri: string) { - let markedRelPath = (await asyncReadFile(scopeUri)) || []; - let markedAbsPath = markedRelPath - .filter((p) => p.length > 0) - .map((relPath) => path.resolve(projectRootUri, relPath)); - //.filter(async (p) => await (await workspace.fs.stat(Uri.parse(p))).type === FileType.File) - this.update(markedAbsPath, []); + let patterns = (await asyncReadFile(scopeUri)) || []; + const ig = ignore().add(patterns); + // find files in all workspace folders, and filter them according to the specified gitignore patterns + // https://git-scm.com/docs/gitignore + let markedAbsPath = (await workspace.findFiles("**/*")) + .map((uri) => path.relative(projectRootUri, uri.fsPath)) + .filter((relPath) => ig.ignores(relPath)) + .map((relPath) => Uri.file(path.resolve(projectRootUri, relPath))); + printChannelOutput(`Loaded patterns from ${scopeUri}`); + this.markOrUnmarkFiles(markedAbsPath); } //write marked files to `scope` file in workspace root - private async writeMarkedFilesToFile() { - if (!Object.keys(this.scopeFilesByProjetRootsURIs).length) { + public async exportMarkedFilesToFile() { + printChannelOutput("Exporting marked files to scope file"); + const fileName = await window.showInputBox({ + placeHolder: "File name", + prompt: "Please enter a name for the exported file", + value: "scope", + }); + if (!fileName) { + return; + } + const scopeFileUrisByFolder = this.getFileURIsByFolderForWS( + fileName, + "txt" + ); + if (!Object.keys(scopeFileUrisByFolder).length) { + printChannelOutput("No opened project - aborting"); return; } // sort by workspace folders @@ -119,19 +140,36 @@ export class DecorationProvider implements FileDecorationProvider { }>((acc: { [key: string]: string[] }, uri: string) => { const workspaceFolder = workspace.getWorkspaceFolder(Uri.parse(uri)); if (workspaceFolder) { + const relativePath = path.relative(workspaceFolder.uri.fsPath, uri); //use relative URIs in the scope file if (!acc[workspaceFolder.uri.fsPath]) { - acc[workspaceFolder.uri.fsPath] = [uri]; + acc[workspaceFolder.uri.fsPath] = [relativePath]; } else { - acc[workspaceFolder.uri.fsPath].push(uri); + acc[workspaceFolder.uri.fsPath].push(relativePath); } } return acc; }, {}); for (const workspaceUri in markedFilesByWS) { - const markedFiles = markedFilesByWS[workspaceUri].join('\n'); - const path = this.scopeFilesByProjetRootsURIs[workspaceUri]; - writeFile(path, markedFiles, { flag: 'w' }, (err) => { + const markedFiles = markedFilesByWS[workspaceUri].join("\n"); + const path = scopeFileUrisByFolder[workspaceUri]; + + try { + await await workspace.fs.stat(Uri.file(path)); + const confirm = await window.showInformationMessage( + "This operation will overwrite the existing scope file. Do you want to continue?", + "Yes", + "No" + ); + if (confirm === "No") { + //abort + return; + } + } catch { + //no existing file => continue + } + printChannelOutput(`Exporting marked files to file ${path}`); + writeFile(path, markedFiles, { flag: "w" }, (err) => { if (err) { console.error( `markfiles: Failed to write scope file with path ${path}. Err: ${err}` @@ -140,4 +178,56 @@ export class DecorationProvider implements FileDecorationProvider { }); } } + + public handleFileRename(oldUri: Uri, newUri: Uri) { + if (!this.markedFiles.has(oldUri.fsPath)) { + return; + } + this.markedFiles.delete(oldUri.fsPath); + getStorage().update(oldUri.fsPath, undefined); + this.markedFiles.add(newUri.fsPath); + getStorage().update(newUri.fsPath, true); + this._onDidChangeFileDecorations.fire(newUri); + } + + private loadMarkedFilesFromWSStorage(): boolean { + if (!getStorage()) { + return false; + } + const markedURIs = getStorage().keys(); + if (!markedURIs.length) { + return false; + } + printChannelOutput(`Loaded files from workspace storage`); + this.markOrUnmarkFiles(markedURIs.map((uri) => Uri.file(uri))); + return true; + } + + private clearMarkedFilesForWS() { + this.markedFiles.forEach((markedFile) => { + this.markedFiles.delete(markedFile); + getStorage().update(markedFile, undefined); + this._onDidChangeFileDecorations.fire(Uri.file(markedFile)); + }); + } + + /** + * @param fileName: name of the file + * @returns an array of URIs for files with name `fileName` for each folder in the workspace + */ + private getFileURIsByFolderForWS(fileName: string, extension: string) { + const scopeFilesByProjectRootsURIs: { [scopeUri: string]: string } = {}; + //iterate over all opened workspace folders + const rootFolders = workspace.workspaceFolders?.map( + (folder) => folder.uri.path + ); + if (!rootFolders) { + return {}; + } + for (const rf of rootFolders) { + const scopeFilePath = path.join(rf, fileName.concat(".", extension)); + scopeFilesByProjectRootsURIs[rf] = scopeFilePath; + } + return scopeFilesByProjectRootsURIs; + } } diff --git a/src/test/example/file1.txt b/src/test/example/file1.txt new file mode 100644 index 0000000..50bd449 --- /dev/null +++ b/src/test/example/file1.txt @@ -0,0 +1 @@ +hello world 1! \ No newline at end of file diff --git a/src/test/example/file2.txt b/src/test/example/file2.txt new file mode 100644 index 0000000..e7dee3b --- /dev/null +++ b/src/test/example/file2.txt @@ -0,0 +1 @@ +hello world 2! \ No newline at end of file diff --git a/src/test/runTest.ts b/src/test/runTest.ts new file mode 100644 index 0000000..3cd9f17 --- /dev/null +++ b/src/test/runTest.ts @@ -0,0 +1,27 @@ +import * as path from "path"; + +import { runTests } from "@vscode/test-electron"; + +async function main() { + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); + + // The path to the extension test script + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); + + // Download VS Code, unzip it and run the integration test + await runTests({ + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: [path.resolve(__dirname, "./example/")], + }); + } catch (err) { + console.error("Failed to run tests"); + process.exit(1); + } +} + +main(); diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts new file mode 100644 index 0000000..07e8af2 --- /dev/null +++ b/src/test/suite/extension.test.ts @@ -0,0 +1,9 @@ +import * as assert from "assert"; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from "vscode"; + +suite("Extension Test Suite", () => { + vscode.window.showInformationMessage("Start all tests."); +}); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts new file mode 100644 index 0000000..d50b5ef --- /dev/null +++ b/src/test/suite/index.ts @@ -0,0 +1,38 @@ +import * as path from "path"; +import * as Mocha from "mocha"; +import * as glob from "glob"; + +export function run(): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: "tdd", + timeout: 1000000, + }); + + const testsRoot = path.resolve(__dirname, ".."); + + return new Promise((c, e) => { + glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run((failures) => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + console.error(err); + e(err); + } + }); + }); +} diff --git a/src/test/suite/provider.test.ts b/src/test/suite/provider.test.ts new file mode 100644 index 0000000..51a5845 --- /dev/null +++ b/src/test/suite/provider.test.ts @@ -0,0 +1,40 @@ +import * as markFilesProvider from "../../fileDecorationProvider"; +import * as vscode from "vscode"; +import path = require("path"); +import { extension, createTestFile, removeTestFile } from "../utils"; + +suite("Provider Test Suite", () => { + let provider: markFilesProvider.DecorationProvider; + let exampleFilesUris: vscode.Uri[]; + let extensionContext: vscode.ExtensionContext; + + // suiteSetup(async () => { + // provider = new markFilesProvider.DecorationProvider(); + // const exampleFiles = ["file1.txt", "file2.txt"].map((fname) => + // path.resolve(__dirname, "../example", fname) + // ); + // exampleFilesUris = exampleFiles.map((p) => vscode.Uri.file(p)); + // }); + + suiteSetup(async () => { + await createTestFile("file3.html"); + extensionContext = await extension.activate(); + }); + + test("Test mark file", async () => { + await vscode.commands.executeCommand("markfiles.markUnmarkSelectedFile"); + // await vscode.commands.executeCommand('markfiles.writeMarkedFilesToDisk'); + console.log("keys : \n"); + console.log(extensionContext); + }); + + suiteTeardown(async () => { + await removeTestFile(); + }); +}); + +function sleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} diff --git a/src/test/utils.ts b/src/test/utils.ts new file mode 100644 index 0000000..045ce39 --- /dev/null +++ b/src/test/utils.ts @@ -0,0 +1,33 @@ +import * as vscode from "vscode"; +import * as assert from "assert"; +import * as path from "path"; +import * as fs from "fs"; + +const packageJSON = JSON.parse( + fs.readFileSync(path.join(__dirname, "../../package.json"), "utf-8") +) as { name: string; publisher: string }; + +export const extension = vscode.extensions.getExtension( + `${packageJSON.publisher}.${packageJSON.name}` +)! as vscode.Extension; + +assert.ok(extension); + +export async function createTestFile( + fileName: string, + content: string = "" +): Promise { + const filePath = path.join( + vscode.workspace.workspaceFolders![0].uri.fsPath, + fileName + ); + fs.writeFileSync(filePath, content); + const uri = vscode.Uri.file(filePath); + await vscode.window.showTextDocument(uri); +} + +export async function removeTestFile(): Promise { + const uri = vscode.window.activeTextEditor?.document.uri as vscode.Uri; + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + await vscode.workspace.fs.delete(uri); +} diff --git a/src/utils.ts b/src/utils.ts index cd499a4..692cb90 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,14 +1,15 @@ -import {promises as fsPromises} from 'fs'; +import { promises as fsPromises } from "fs"; export async function asyncReadFile(path: string) { try { - const contents = await fsPromises.readFile(path, 'utf-8'); + const contents = await fsPromises.readFile(path, "utf-8"); const arr = contents.split(/\r?\n/); return arr; } catch (err) { - console.error(`markfiles: Failed to read file with path ${path} from disk. err: ${err}`); + console.error( + `markfiles: Failed to read file with path ${path} from disk. err: ${err}` + ); } } - diff --git a/tsconfig.json b/tsconfig.json index 315af7e..c5bff67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,15 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "ES2020", - "outDir": "out", - "lib": [ - "ES2020" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true /* enable all strict type-checking options */ - /* Additional Checks */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - } + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "lib": ["ES2020"], + "sourceMap": true, + "rootDir": "src", + "strict": true /* enable all strict type-checking options */ + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + } } diff --git a/yarn.lock b/yarn.lock index e577591..bb30424 100644 --- a/yarn.lock +++ b/yarn.lock @@ -732,7 +732,7 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==