Skip to content

Commit

Permalink
feat(MoveFile): add typeahead support
Browse files Browse the repository at this point in the history
  • Loading branch information
sleistner committed Jan 23, 2023
1 parent 6d4359a commit 0e3e0ca
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 12 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@
"markdownDeprecationMessage": "**Deprecated**: Please use `#fileutils.newFile.typeahead.enabled#` or `#fileutils.newFolder.typeahead.enabled#` instead.",
"deprecationMessage": "Deprecated: Please use fileutils.newFile.typeahead.enabled or fileutils.newFolder.typeahead.enabled instead."
},
"fileutils.moveFile.typeahead.enabled": {
"type": "boolean",
"default": false,
"description": "Controls wheather to show a directory selector for move file command."
},
"fileutils.newFile.typeahead.enabled": {
"type": "boolean",
"default": true,
Expand Down
4 changes: 3 additions & 1 deletion src/command/MoveFileCommand.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Uri } from "vscode";
import { MoveFileController } from "../controller/MoveFileController";
import { getConfiguration } from "../lib/config";
import { BaseCommand } from "./BaseCommand";

export class MoveFileCommand extends BaseCommand<MoveFileController> {
public async execute(uri?: Uri): Promise<void> {
const dialogOptions = { prompt: "New Location", showFullPath: true, uri };
const typeahead = getConfiguration("moveFile.typeahead.enabled") === true;
const dialogOptions = { prompt: "New Location", showFullPath: true, uri, typeahead };
const fileItem = await this.controller.showDialog(dialogOptions);
await this.executeController(fileItem);
}
Expand Down
56 changes: 46 additions & 10 deletions src/controller/MoveFileController.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import expand from "brace-expansion";
import * as path from "path";
import { FileType, Uri, window, workspace } from "vscode";
import { FileItem } from "../FileItem";
Expand All @@ -10,25 +11,21 @@ export interface MoveFileDialogOptions extends DialogOptions {

export class MoveFileController extends BaseFileController {
public async showDialog(options: MoveFileDialogOptions): Promise<FileItem | undefined> {
const { prompt, showFullPath = false, uri } = options;
const { uri } = options;
const sourcePath = await this.getSourcePath({ uri });

if (!sourcePath) {
throw new Error();
}

const value = showFullPath ? sourcePath : path.basename(sourcePath);
const valueSelection = this.getFilenameSelection(value);
const targetPath = await window.showInputBox({
prompt,
value,
valueSelection,
});
const targetPath = await this.getTargetPath(sourcePath, options);

if (targetPath) {
const isDir = (await workspace.fs.stat(Uri.file(sourcePath))).type === FileType.Directory;
const realPath = path.resolve(path.dirname(sourcePath), targetPath);
return new FileItem(sourcePath, realPath, isDir);

return expand(targetPath)
.map((filePath) => new FileItem(sourcePath, filePath, isDir))
.at(0);
}
}

Expand All @@ -38,6 +35,45 @@ export class MoveFileController extends BaseFileController {
return fileItem.move();
}

private async getTargetPath(sourcePath: string, options: MoveFileDialogOptions): Promise<string | undefined> {
const { prompt } = options;
const value = await this.getTargetPathPromptValue(sourcePath, options);
const valueSelection = this.getFilenameSelection(value);

return await window.showInputBox({
prompt,
value,
valueSelection,
});
}

private async getTargetPathPromptValue(sourcePath: string, options: MoveFileDialogOptions): Promise<string> {
const { showFullPath = false } = options;
if (showFullPath) {
return await this.getFullTargetPathPromptValue(sourcePath, options);
}
return path.basename(sourcePath);
}

private async getFullTargetPathPromptValue(sourcePath: string, options: MoveFileDialogOptions): Promise<string> {
const { typeahead } = options;

if (!typeahead) {
return sourcePath;
}

const workspaceSourcePath = await this.getWorkspaceSourcePath();

if (!workspaceSourcePath) {
throw new Error();
}

const rootPath = await this.getFileSourcePathAtRoot(workspaceSourcePath, { relativeToRoot: true, typeahead });
const fileName = path.basename(sourcePath);

return path.join(rootPath, fileName);
}

private getFilenameSelection(value: string): [number, number] {
const basename = path.basename(value);
const start = value.length - basename.length;
Expand Down
50 changes: 49 additions & 1 deletion test/command/MoveFileCommand.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { expect } from "chai";
import path from "path";
import sinon from "sinon";
import { Uri, window, workspace } from "vscode";
import { MoveFileCommand } from "../../src/command";
import { MoveFileController } from "../../src/controller";
import * as helper from "../helper";

describe(MoveFileCommand.name, () => {
const subject = new MoveFileCommand(new MoveFileController(helper.createExtensionContext()));

beforeEach(helper.beforeEach);
beforeEach(async () => {
await helper.beforeEach();
helper.createGetConfigurationStub({ "moveFile.typeahead.enabled": false });
});

afterEach(helper.afterEach);

Expand All @@ -14,16 +21,57 @@ describe(MoveFileCommand.name, () => {
beforeEach(async () => {
await helper.openDocument(helper.editorFile1);
helper.createShowInputBoxStub().resolves(helper.targetFile.path);
helper.createShowQuickPickStub().resolves({ label: "/", description: "" });
});

afterEach(async () => {
await helper.closeAllEditors();
helper.restoreShowInputBox();
helper.restoreShowQuickPick();
});

helper.protocol.it("should prompt for file destination", subject, "New Location");
helper.protocol.it("should move current file to destination", subject);
helper.protocol.describe("with target file in non-existent nested directory", subject);

describe("configuration", () => {
describe('when "newFile.typeahead.enabled" is "true"', () => {
beforeEach(async () => {
await workspace.fs.createDirectory(Uri.file(path.resolve(helper.tmpDir.fsPath, "dir-1")));
await workspace.fs.createDirectory(Uri.file(path.resolve(helper.tmpDir.fsPath, "dir-2")));

helper.createGetConfigurationStub({ "moveFile.typeahead.enabled": true });
helper
.createStubObject(workspace, "workspaceFolders")
.get(() => [{ uri: Uri.file(helper.tmpDir.fsPath), name: "a", index: 0 }]);
});

it("should show the quick pick dialog", async () => {
await subject.execute();
expect(window.showQuickPick).to.have.been.calledOnceWith(
sinon.match([
{ description: "- workspace root", label: "/" },
{ description: undefined, label: "/dir-1" },
{ description: undefined, label: "/dir-2" },
]),
sinon.match({
placeHolder: helper.quickPick.typeahead.placeHolder,
})
);
});
});

describe('when "newFile.typeahead.enabled" is "false"', () => {
beforeEach(async () => {
helper.createGetConfigurationStub({ "moveFile.typeahead.enabled": false });
});

it("should not show the quick pick dialog", async () => {
await subject.execute();
expect(window.showQuickPick).to.have.not.been.called;
});
});
});
});

helper.protocol.describe("without an open text document", subject);
Expand Down

0 comments on commit 0e3e0ca

Please sign in to comment.