Skip to content

Commit

Permalink
feat(NewFile): add typeahead setting
Browse files Browse the repository at this point in the history
  • Loading branch information
sleistner committed Jan 23, 2023
1 parent 96b5483 commit 764f614
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 70 deletions.
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,19 @@
"fileutils.typeahead.enabled": {
"type": "boolean",
"default": true,
"description": "Controls if directory selector should be shown."
"description": "Controls wheather to show a directory selector for new file and new folder command.",
"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.newFile.typeahead.enabled": {
"type": "boolean",
"default": true,
"description": "Controls wheather to show a directory selector for new file command."
},
"fileutils.newFolder.typeahead.enabled": {
"type": "boolean",
"default": true,
"description": "Controls wheather to show a directory selector for new folder command."
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/command/NewFileCommand.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { NewFileController } from "../controller/NewFileController";
import { getConfiguration } from "../lib/config";
import { BaseCommand } from "./BaseCommand";

export class NewFileCommand extends BaseCommand<NewFileController> {
public async execute(): Promise<void> {
const typeahead = this.typeahead;
const relativeToRoot = this.options?.relativeToRoot ?? false;
const dialogOptions = { prompt: "File Name", relativeToRoot };
const dialogOptions = { prompt: "File Name", relativeToRoot, typeahead };
const fileItems = await this.controller.showDialog(dialogOptions);

if (fileItems) {
Expand All @@ -15,4 +17,8 @@ export class NewFileCommand extends BaseCommand<NewFileController> {
await Promise.all(executions);
}
}

protected get typeahead(): boolean {
return (getConfiguration("newFile.typeahead.enabled") ?? getConfiguration("typeahead.enabled")) === true;
}
}
35 changes: 34 additions & 1 deletion src/controller/BaseFileController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { commands, env, ExtensionContext, TextEditor, window, workspace } from "vscode";
import { commands, env, ExtensionContext, TextEditor, Uri, window, workspace, WorkspaceFolder } from "vscode";
import { FileItem } from "../FileItem";
import { Cache } from "../lib/Cache";
import { DialogOptions, ExecuteOptions, FileController, GetSourcePathOptions } from "./FileController";
import { TypeAheadController } from "./TypeAheadController";

export abstract class BaseFileController implements FileController {
constructor(protected context: ExtensionContext) {}
Expand Down Expand Up @@ -99,4 +100,36 @@ export abstract class BaseFileController implements FileController {
// 6. Return the clipboard data from the API call (which could be an empty string if it failed).
return postAPICallClipboardData;
}

protected async getWorkspaceSourcePath(): Promise<string | undefined> {
const workspaceFolder = await this.selectWorkspaceFolder();
return workspaceFolder?.uri.fsPath;
}

protected async selectWorkspaceFolder(): Promise<WorkspaceFolder | undefined> {
if (workspace.workspaceFolders && workspace.workspaceFolders.length === 1) {
return workspace.workspaceFolders[0];
}

const sourcePath = await this.getSourcePath({ ignoreIfNotExists: true });
const uri = Uri.file(sourcePath);
return workspace.getWorkspaceFolder(uri) || window.showWorkspaceFolderPick();
}

protected async getFileSourcePathAtRoot(rootPath: string, options: GetSourcePathOptions): Promise<string> {
const { relativeToRoot = false, typeahead } = options;
let sourcePath = rootPath;

if (typeahead) {
const cache = this.getCache(`workspace:${sourcePath}`);
const typeAheadController = new TypeAheadController(cache, relativeToRoot);
sourcePath = await typeAheadController.showDialog(sourcePath);
}

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

return sourcePath;
}
}
2 changes: 2 additions & 0 deletions src/controller/FileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FileItem } from "../FileItem";
export interface DialogOptions {
prompt?: string;
uri?: Uri;
typeahead?: boolean;
}

export interface ExecuteOptions {
Expand All @@ -14,6 +15,7 @@ export interface GetSourcePathOptions {
relativeToRoot?: boolean;
ignoreIfNotExists?: boolean;
uri?: Uri;
typeahead?: boolean;
}

export interface FileController {
Expand Down
52 changes: 11 additions & 41 deletions src/controller/NewFileController.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import expand from "brace-expansion";
import * as path from "path";
import { Uri, window, workspace, WorkspaceFolder } from "vscode";
import { window } from "vscode";
import { FileItem } from "../FileItem";
import { getConfiguration } from "../lib/config";
import { BaseFileController } from "./BaseFileController";
import { DialogOptions, ExecuteOptions, GetSourcePathOptions } from "./FileController";
import { TypeAheadController } from "./TypeAheadController";
import expand from "brace-expansion";

export interface NewFileDialogOptions extends Omit<DialogOptions, "uri"> {
relativeToRoot?: boolean;
Expand All @@ -17,8 +15,8 @@ export interface NewFileExecuteOptions extends ExecuteOptions {

export class NewFileController extends BaseFileController {
public async showDialog(options: NewFileDialogOptions): Promise<FileItem[] | undefined> {
const { prompt, relativeToRoot = false } = options;
const sourcePath = await this.getSourcePath({ relativeToRoot });
const { prompt, relativeToRoot = false, typeahead } = options;
const sourcePath = await this.getNewFileSourcePath({ relativeToRoot, typeahead });
const value: string = path.join(sourcePath, path.sep);
const valueSelection: [number, number] = [value.length, value.length];
const targetPath = await window.showInputBox({
Expand Down Expand Up @@ -46,48 +44,20 @@ export class NewFileController extends BaseFileController {
}
}

public async getSourcePath({ relativeToRoot }: GetSourcePathOptions): Promise<string> {
const rootPath = await (relativeToRoot ? this.getWorkspaceSourcePath() : this.getFileSourcePath());
public async getNewFileSourcePath({ relativeToRoot, typeahead }: GetSourcePathOptions): Promise<string> {
const rootPath = await this.getRootPath(relativeToRoot === true);

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

return this.getFileSourcePathAtRoot(rootPath, relativeToRoot === true);
}

private async getWorkspaceSourcePath(): Promise<string | undefined> {
const workspaceFolder = await this.selectWorkspaceFolder();
return workspaceFolder?.uri.fsPath;
return this.getFileSourcePathAtRoot(rootPath, { relativeToRoot, typeahead });
}

private async selectWorkspaceFolder(): Promise<WorkspaceFolder | undefined> {
if (workspace.workspaceFolders && workspace.workspaceFolders.length === 1) {
return workspace.workspaceFolders[0];
private async getRootPath(relativeToRoot: boolean): Promise<string | undefined> {
if (relativeToRoot) {
return this.getWorkspaceSourcePath();
}

const sourcePath = await super.getSourcePath({ ignoreIfNotExists: true });
const uri = Uri.file(sourcePath);
return workspace.getWorkspaceFolder(uri) || window.showWorkspaceFolderPick();
}

private async getFileSourcePath(): Promise<string> {
return path.dirname(await super.getSourcePath());
}

private async getFileSourcePathAtRoot(rootPath: string, relativeToRoot: boolean): Promise<string> {
let sourcePath = rootPath;

if (getConfiguration("typeahead.enabled") === true) {
const cache = this.getCache(`workspace:${sourcePath}`);
const typeAheadController = new TypeAheadController(cache, relativeToRoot);
sourcePath = await typeAheadController.showDialog(sourcePath);
}

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

return sourcePath;
return path.dirname(await this.getSourcePath());
}
}
2 changes: 1 addition & 1 deletion src/controller/TypeAheadController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ async function waitForIOEvents(): Promise<void> {
return new Promise((resolve) => setImmediate(resolve));
}
export class TypeAheadController {
constructor(private cache: Cache, private relativeToRoot: boolean) {}
constructor(private cache: Cache, private relativeToRoot: boolean = false) {}

public async showDialog(sourcePath: string): Promise<string> {
const items = await this.buildQuickPickItems(sourcePath);
Expand Down
46 changes: 21 additions & 25 deletions test/command/NewFileCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { NewFileController } from "../../src/controller";
import * as helper from "../helper";

describe(NewFileCommand.name, () => {
const hint = "larger projects may take a moment to load";
const expectedShowQuickPickPlaceHolder = `First, select an existing path to create relative to (${hint})`;

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

afterEach(helper.afterEach);

Expand Down Expand Up @@ -44,13 +44,9 @@ describe(NewFileCommand.name, () => {
await workspace.fs.createDirectory(Uri.file(path.resolve(helper.tmpDir.fsPath, "dir-2")));
});

afterEach(async () => {
helper.restoreGetConfiguration();
});

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

it("should show the quick pick dialog", async () => {
Expand All @@ -62,18 +58,18 @@ describe(NewFileCommand.name, () => {
{ description: undefined, label: "/dir-2" },
]),
sinon.match({
placeHolder: expectedShowQuickPickPlaceHolder,
placeHolder: helper.quickPick.typeahead.placeHolder,
})
);
});
});

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

it("should show the quick pick dialog", async () => {
it("should not show the quick pick dialog", async () => {
await subject.execute();
expect(window.showQuickPick).to.have.not.been.called;
});
Expand Down Expand Up @@ -185,9 +181,9 @@ describe(NewFileCommand.name, () => {
helper.restoreGetConfiguration();
});

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

it("should show the quick pick dialog", async () => {
Expand All @@ -199,18 +195,18 @@ describe(NewFileCommand.name, () => {
{ description: undefined, label: "/dir-2" },
]),
sinon.match({
placeHolder: expectedShowQuickPickPlaceHolder,
placeHolder: helper.quickPick.typeahead.placeHolder,
})
);
});
});

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

it("should show the quick pick dialog", async () => {
it("should not show the quick pick dialog", async () => {
await subject.execute();
expect(window.showQuickPick).to.have.not.been.called;
});
Expand Down Expand Up @@ -267,9 +263,9 @@ describe(NewFileCommand.name, () => {
helper.restoreGetConfiguration();
});

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

it("should show the quick pick dialog", async () => {
Expand All @@ -282,15 +278,15 @@ describe(NewFileCommand.name, () => {
{ description: undefined, label: "/dir-2" },
]),
sinon.match({
placeHolder: expectedShowQuickPickPlaceHolder,
placeHolder: helper.quickPick.typeahead.placeHolder,
})
);
});
});

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

it("should not show the quick pick dialog", async () => {
Expand Down
2 changes: 2 additions & 0 deletions test/helper/callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { existsSync } from "fs";
import { workspace } from "vscode";
import { editorFile1, editorFile2, fixtureFile1, fixtureFile2, tmpDir } from "./environment";
import { restoreGetConfiguration } from "./stubs";

export async function beforeEach(): Promise<void> {
if (existsSync(tmpDir.fsPath)) {
Expand All @@ -14,4 +15,5 @@ export async function afterEach(): Promise<void> {
if (existsSync(tmpDir.fsPath)) {
await workspace.fs.delete(tmpDir, { recursive: true, useTrash: false });
}
restoreGetConfiguration();
}
6 changes: 6 additions & 0 deletions test/helper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ export const protocol = {
return mocha.it(name, step);
},
};

export const quickPick = {
typeahead: {
placeHolder: "First, select an existing path to create relative to (larger projects may take a moment to load)",
},
};

0 comments on commit 764f614

Please sign in to comment.