From bff34391d033bf7cc268ccbf72ee15409789cba8 Mon Sep 17 00:00:00 2001 From: Rob O'Leary <3703647+robole@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:40:11 +0100 Subject: [PATCH] Added `File Bunny: Move Folder` command. --- CHANGELOG.md | 10 +++ README.md | 13 +++- package.json | 7 +- src/extension.js | 14 +++- src/folderAction.js | 14 ++++ src/moveFolderPicker.js | 166 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 src/moveFolderPicker.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c20ba..3bb200f 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/-0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.0] - 2024-06-11 + +### Added + +- Added `File Bunny: Move Folder` command. + +### Changed + +- Added another subheading level to the "Commands" section in the README. A level 2 section for "File Actions" and "Folder Actions". + ## [2.3.0] - 2024-06-06 ### Changed diff --git a/README.md b/README.md index a660c48..1106bc2 100755 --- a/README.md +++ b/README.md @@ -83,7 +83,9 @@ You may want to edit a file in an app specific to that file type. For example, I The following commands can be run from the Command Palette (`Ctrl+Shift+P`). They are categorised below: -### File actions on the current workspace +### File actions + +#### File actions on the current workspace 1. `File Bunny: Open File`: Choose a file to open from the current workspace. 1. `File Bunny: Open File to the Right`: Choose a file to open from the current workspace, and split it to the right of the active editor. @@ -96,7 +98,7 @@ The following commands can be run from the Command Palette (`Ctrl+Shift+P`). The 1. `File Bunny: Duplicate File`: Choose a file to duplicate to another location in the current workspace. 1. `File Bunny: Delete File`: Choose a file to delete from the current workspace. The file is put into the trash (recycle bin). -### File actions on the active file +#### File actions on the active file 1. `File Bunny: Open Active File in External Default App`: Open the active file in the system default app. 1. `File Bunny: Move Active File`: Move the active file to another location in the current workspace. @@ -106,13 +108,18 @@ The following commands can be run from the Command Palette (`Ctrl+Shift+P`). The ### Folder actions +There are 2 contexts for the folder actions: system (any folder anywhere) and current workspace (folders within open workspace). + +#### Folder actions on system + 1. `File Bunny: Open Folder`: Choose a folder to open as the workspace. -### Folder actions on the current workspace +#### Folder actions on the current workspace 1. `File Bunny: Open Workspace Folder in External Default App`: Open the current workspace folder in the system file explorer. 1. `File Bunny: Open Folder in External Default App`: Open a folder from the current workspace in the system file explorer. 1. `File Bunny: Create New Folder`: Create a new folder in the current workspace. +1. `File Bunny: Move Folder`: Move a folder from the current workspace, and place it somewhere else in the current workspace. ![recently added](img/new.png) 1. `File Bunny: Duplicate Folder`: Duplicate a folder from the current workspace, and place it somewhere in the current workspace. 1. `File Bunny: Delete Folder`: Delete a folder from the current workspace. diff --git a/package.json b/package.json index 4daad1a..93cb676 100755 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "description": "Perform file actions quickly with keyboard-driven file selection. 🐰⌨️", "icon": "img/logo.png", - "version": "2.3.0", + "version": "2.4.0", "engines": { "vscode": "^1.84.0", "node": ">=12" @@ -197,6 +197,11 @@ "category": "File Bunny", "title": "Create New Folder" }, + { + "command": "filebunny.moveFolder", + "category": "File Bunny", + "title": "Move Folder" + }, { "command": "filebunny.duplicateFolder", "category": "File Bunny", diff --git a/src/extension.js b/src/extension.js index 17b63d0..f3a7cef 100644 --- a/src/extension.js +++ b/src/extension.js @@ -168,27 +168,34 @@ function activate(context) { ); let disposable24 = vscode.commands.registerCommand( + "filebunny.moveFolder", + async () => { + await folderAction.moveFolder(); + } + ); + + let disposable25 = vscode.commands.registerCommand( "filebunny.duplicateFolder", async () => { await folderAction.duplicateFolder(); } ); - let disposable25 = vscode.commands.registerCommand( + let disposable26 = vscode.commands.registerCommand( "filebunny.deleteFolder", async () => { await folderAction.deleteFolder(); } ); - let disposable26 = vscode.commands.registerCommand( + let disposable27 = vscode.commands.registerCommand( "filebunny.openFileExternal", async () => { await fileAction.openFileExternal(); } ); - let disposable27 = vscode.commands.registerCommand( + let disposable28 = vscode.commands.registerCommand( "filebunny.openFolderExternal", async () => { await folderAction.openFolderExternal(); @@ -223,6 +230,7 @@ function activate(context) { disposable25, disposable26, disposable27, + disposable28, ]; } diff --git a/src/folderAction.js b/src/folderAction.js index e09c9e6..c158a3b 100644 --- a/src/folderAction.js +++ b/src/folderAction.js @@ -8,6 +8,7 @@ const Browser = require("./browser"); const globPicker = require("./globPicker"); const NewFolderPicker = require("./newFolderPicker"); const DuplicateFolderPicker = require("./duplicateFolderPicker"); +const MoveFolderPicker = require("./moveFolderPicker"); const fileSystem = require("./fileSystem"); async function openRecentFolder() { @@ -73,6 +74,18 @@ async function createFolder() { await picker.run(); } +async function moveFolder() { + if (util.isWorkspaceOpen() === false) { + vscode.window.showWarningMessage( + "Cannot move a folder. There is no workspace open." + ); + return; + } + + let picker = new MoveFolderPicker(); + await picker.run(); +} + async function duplicateFolder() { if (util.isWorkspaceOpen() === false) { vscode.window.showWarningMessage( @@ -177,6 +190,7 @@ module.exports = { openRecentFolder, openFolder, createFolder, + moveFolder, duplicateFolder, deleteFolder, openWorkspaceFolderExternal, diff --git a/src/moveFolderPicker.js b/src/moveFolderPicker.js new file mode 100644 index 0000000..68c8dec --- /dev/null +++ b/src/moveFolderPicker.js @@ -0,0 +1,166 @@ +// @ts-nocheck +// eslint-disable-next-line node/no-missing-require, import/no-unresolved +const vscode = require("vscode"); +const nodePath = require("path"); +const Step = require("./step"); +const globPicker = require("./globPicker"); +const MultiStepPicker = require("./multiStepPicker"); +const configuration = require("./configuration"); +const util = require("./util"); + +class MoveFolderPicker extends MultiStepPicker { + constructor() { + let steps = [ + new Step(1, `Move Folder`, "Pick a folder to move", []), + new Step( + 2, + `Move to`, + "Pick a location for the moved folder", + [], + [vscode.QuickInputButtons.Back] + ), + ]; + + super(steps); + + if (vscode.workspace.workspaceFolders !== undefined) { + this.rootFolder = vscode.workspace.workspaceFolders[0].uri.fsPath; + } + + let disposable1 = this.picker.onDidChangeValue( + this.onDidChangeValue.bind(this) + ); + this.disposables.push(disposable1); + + let disposable2 = this.picker.onDidChangeActive( + this.onDidChangeActive.bind(this) + ); + this.disposables.push(disposable2); + } + + async run() { + if (vscode.workspace.workspaceFolders === undefined) { + return; + } + this.picker.show(); + + this.picker.busy = true; + + let absoluteExcludes = configuration.getAbsoluteExcludes(this.rootFolder); + + let folderList = await globPicker.getFilesRecursivelyAsPickerItems( + this.rootFolder, + { + showFiles: false, + showFolders: true, + excludes: absoluteExcludes, + includeTopFolder: true, + topFolderDescription: "Workspace Root", + } + ); + this.steps[0].items = folderList; + this.steps[1].items = folderList; + + this.setCurrentStep(this.steps[0]); + this.picker.busy = false; + } + + async onDidAccept() { + if (this.currentStepNum === 1) { + let pickedItem = this.picker.selectedItems[0].name; + + let currentIndex = this.currentStepNum - 1; + this.steps[currentIndex].value = pickedItem; + + this.absoluteFromPath = nodePath.join( + this.rootFolder, + this.steps[0].value + ); + + this.picker.value = ""; + this.setCurrentStep(this.steps[currentIndex + 1]); + } else if (this.currentStepNum === 2) { + let pickedItem = this.picker.selectedItems[0].name; + + let currentIndex = this.currentStepNum - 1; + this.steps[currentIndex].value = pickedItem; + this.move(); + } + } + + goBack() { + if (this.currentStepNum > 1) { + let previousIndex = this.currentStepNum - 2; + let currentIndex = this.currentStepNum - 1; + + this.picker.enabled = false; + this.steps[currentIndex].value = ""; + this.setCurrentStep(this.steps[previousIndex]); + + // required to clear filter if text was entered in last step + setTimeout(() => { + this.picker.value = this.steps[previousIndex].value; + this.picker.items = this.steps[previousIndex].items; + }, 20); + this.picker.enabled = true; + } + } + + onDidChangeValue() { + this.onDidChangeActive(); // same behaviour desirable for any input change + } + + onDidChangeActive() { + if (this.currentStepNum === 1) { + let folderName = this.picker.activeItems[0].name; + let truncatedName = util.truncateName(folderName); + this.setTitle(`Move '${truncatedName}'`); + } else if (this.currentStepNum === 2) { + let truncatedFromFolder = util.truncateName(this.steps[0].value); + let truncatedToFolder = util.truncateName( + this.picker.activeItems[0].name + ); + + this.setTitle(`Move '${truncatedFromFolder}' to '${truncatedToFolder}'`); + } + } + + setTitle(value) { + this.picker.title = value; + } + + async move() { + let absoluteFromUri = vscode.Uri.file(this.absoluteFromPath); + + let absoluteToPath; + let toFolder = nodePath.basename(this.absoluteFromPath); + + if (this.steps[1].value === globPicker.currentFolderName) { + absoluteToPath = nodePath.join(this.rootFolder, toFolder); + } else { + absoluteToPath = nodePath.join( + this.rootFolder, + this.steps[1].value, + toFolder + ); + } + + let absoluteToUri = vscode.Uri.file(absoluteToPath); + + try { + await vscode.workspace.fs.copy(absoluteFromUri, absoluteToUri, { + overwrite: false, + }); + await vscode.workspace.fs.delete(absoluteFromUri, { + recursive: true, + boolean: true, + }); + } catch (err) { + vscode.window.showErrorMessage(err.message); + } + + this.close(); + } +} + +module.exports = MoveFolderPicker;