Skip to content

Commit

Permalink
Merge pull request #135 from sleistner/fix/selector-no-longer-displays
Browse files Browse the repository at this point in the history
NewFileCommand - fix: show folder selector
  • Loading branch information
sleistner authored Aug 23, 2019
2 parents 7c0ffb3 + 91adf5d commit b638c84
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 181 deletions.
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
"--extensionDevelopmentPath=${workspaceFolder}",
"--folder-uri=/tmp"
],
"outFiles": [
"${workspaceFolder}/out/src/**/*.js"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
"semantic-release-vsce": "^2.2.8",
"sinon": "^7.4.1",
"sinon-chai": "^3.3.0",
"tslint": "^5.12.1",
"tslint": "^5.19.0",
"tslint-no-unused-expression-chai": "^0.1.4",
"typescript": "^3.5.3",
"vscode-test": "^1.2.0"
Expand Down
4 changes: 2 additions & 2 deletions src/controller/BaseFileController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { commands, env, ExtensionContext, TextEditor, ViewColumn, window, workspace } from 'vscode';
import { FileItem } from '../FileItem';
import { Cache } from '../lib/Cache';
import { IDialogOptions, IExecuteOptions, IFileController } from './FileController';
import { IDialogOptions, IExecuteOptions, IFileController, IGetSourcePathOptions } from './FileController';

export abstract class BaseFileController implements IFileController {
constructor(protected context: ExtensionContext) { }
Expand Down Expand Up @@ -32,7 +32,7 @@ export abstract class BaseFileController implements IFileController {
return commands.executeCommand('workbench.action.closeActiveEditor');
}

public async getSourcePath(): Promise<string> {
public async getSourcePath(options?: IGetSourcePathOptions): Promise<string> {
// Attempting to get the fileName from the activeTextEditor.
// Works for text files only.
const activeEditor = window.activeTextEditor;
Expand Down
6 changes: 5 additions & 1 deletion src/controller/FileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ export interface IExecuteOptions {
fileItem: FileItem;
}

export interface IGetSourcePathOptions {
relativeToRoot?: boolean;
}

export interface IFileController {
showDialog(options?: IDialogOptions): Promise<FileItem | undefined>;
execute(options: IExecuteOptions): Promise<FileItem>;
openFileInEditor(fileItem: FileItem): Promise<TextEditor | undefined>;
closeCurrentFileEditor(): Promise<any>;
getSourcePath(): Promise<string>;
getSourcePath(options?: IGetSourcePathOptions): Promise<string>;
}
52 changes: 31 additions & 21 deletions src/controller/NewFileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Uri, window, workspace, WorkspaceFolder } from 'vscode';
import { FileItem } from '../FileItem';
import { getConfiguration } from '../lib/config';
import { BaseFileController } from './BaseFileController';
import { IDialogOptions, IExecuteOptions } from './FileController';
import { IDialogOptions, IExecuteOptions, IGetSourcePathOptions } from './FileController';
import { TypeAheadController } from './TypeAheadController';

export interface INewFileDialogOptions extends IDialogOptions {
Expand All @@ -18,10 +18,7 @@ export class NewFileController extends BaseFileController {

public async showDialog(options: INewFileDialogOptions): Promise<FileItem | undefined> {
const { prompt, relativeToRoot = false } = options;
const sourcePath = await this.findSourcePath(relativeToRoot);
if (!sourcePath) {
throw new Error();
}
const sourcePath = await this.getSourcePath({ relativeToRoot });
const value: string = path.join(sourcePath, path.sep);
const valueSelection: [number, number] = [value.length, value.length];
const targetPath = await window.showInputBox({ prompt, value, valueSelection });
Expand All @@ -42,23 +39,16 @@ export class NewFileController extends BaseFileController {
}
}

private async findSourcePath(relativeToRoot: boolean): Promise<string | undefined> {
if (relativeToRoot) {
return this.getWorkspaceSourcePath();
}
return this.getFileSourcePath();
}

private async getFileSourcePath(): Promise<string> {
let sourcePath = await this.getSourcePath();
sourcePath = path.dirname(sourcePath);
public async getSourcePath({ relativeToRoot }: IGetSourcePathOptions): Promise<string> {
const rootPath = relativeToRoot
? await this.getWorkspaceSourcePath()
: await this.getFileSourcePath();

if (getConfiguration('typeahead.enabled') === true) {
const typeAheadController = new TypeAheadController();
const cache = this.getCache(`workspace:${sourcePath}`);
sourcePath = await typeAheadController.showDialog(sourcePath, cache);
if (!rootPath) {
throw new Error();
}
return sourcePath;

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

private async getWorkspaceSourcePath(): Promise<string | undefined> {
Expand All @@ -71,8 +61,28 @@ export class NewFileController extends BaseFileController {
return workspace.workspaceFolders[0];
}

const sourcePath = await this.getSourcePath();
const sourcePath = await super.getSourcePath();
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;
}
}
55 changes: 32 additions & 23 deletions src/controller/TypeAheadController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,61 @@ import { TreeWalker } from '../lib/TreeWalker';

export class TypeAheadController {

public async showDialog(sourcePath: string, cache: Cache): Promise<string> {
const choices = await this.buildChoices(sourcePath, cache);
constructor(private cache: Cache, private relativeToRoot: boolean) { }

if (choices.length < 2) {
public async showDialog(sourcePath: string): Promise<string> {
const items = await this.buildQuickPickItems(sourcePath);

if (items.length < 2) {
return sourcePath;
}

const item = await this.showQuickPick(choices);
const item = await this.showQuickPick(items);

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

const selection = item.label;
cache.put('last', selection);
this.cache.put('last', selection);

return path.join(sourcePath, selection);
}

private async buildChoices(sourcePath: string, cache: Cache): Promise<QuickPickItem[]> {
const treeWalker = new TreeWalker();
private async buildQuickPickItems(sourcePath: string): Promise<QuickPickItem[]> {
const directories = await this.listDirectoriesAtSourcePath(sourcePath);
return [
...this.buildQuickPickItemsHeader(),
...directories.map((directory) => this.buildQuickPickItem(directory))
];
}

return treeWalker.directories(sourcePath)
.then(this.toQuickPickItems)
.then(this.prependChoice('/', '- workspace root'))
.then(this.prependChoice(cache.get('last'), '- last selection'));
private async listDirectoriesAtSourcePath(sourcePath: string): Promise<string[]> {
const treeWalker = new TreeWalker();
return treeWalker.directories(sourcePath);
}

private prependChoice(label: string, description: string): (choices: QuickPickItem[]) => QuickPickItem[] {
return (choices) => {
if (label) {
const choice = { description, label };
choices.unshift(choice);
}
return choices;
};
private buildQuickPickItemsHeader(): QuickPickItem[] {
const items = [
this.buildQuickPickItem('/', `- ${this.relativeToRoot ? 'workspace root' : 'current file'}`)
];

const lastEntry = this.cache.get('last');
if (lastEntry) {
items.push(this.buildQuickPickItem(lastEntry, '- last selection'));
}

return items;
}

private async toQuickPickItems(choices: string[]): Promise<QuickPickItem[]> {
return choices.map((choice) => ({ label: choice, description: undefined }));
private buildQuickPickItem(label: string, description?: string | undefined): QuickPickItem {
return { description, label };
}

private async showQuickPick(choices: QuickPickItem[]) {
private async showQuickPick(items: QuickPickItem[]) {
const placeHolder = `
First, select an existing path to create relative to (larger projects may take a moment to load)
`;
return window.showQuickPick<QuickPickItem>(choices, { placeHolder });
return window.showQuickPick<QuickPickItem>(items, { placeHolder });
}
}
30 changes: 11 additions & 19 deletions src/lib/TreeWalker.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import * as fs from 'fs';
import { sync as globSync } from 'glob';
import * as glob from 'glob';
import * as path from 'path';
import { workspace } from 'vscode';
import { Uri, workspace } from 'vscode';
import { getConfiguration } from '../lib/config';

export class TreeWalker {

public async directories(sourcePath: string): Promise<string[]> {
const ignore = this.configIgnoredGlobs()
.map(this.invertGlob);

const results = globSync('**', { cwd: sourcePath, ignore })
.filter((file) => fs.statSync(path.join(sourcePath, file)).isDirectory())
.map((file) => path.sep + file);

return results;
return glob
.sync('**/', { cwd: sourcePath, ignore: this.configIgnore() })
.map((file) => path.sep + file.replace(/\/$/, ''));
}

private configIgnoredGlobs(): string[] {
const configFilesExclude = {
private configIgnore(): string[] {
const excludedFilesConfig = {
...getConfiguration('typeahead.exclude'),
...workspace.getConfiguration('files.exclude', null)
};
return Object.keys(configFilesExclude)
.filter((key) => configFilesExclude[key] === true);
}

private invertGlob(pattern: string) {
return pattern.replace(/^!/, '');
return Object
.keys(excludedFilesConfig)
.filter((key) => excludedFilesConfig[key] === true)
.map(((pattern) => pattern.replace(/^!/, '')));
}
}
9 changes: 2 additions & 7 deletions test/command/MoveFileCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('MoveFileCommand', () => {
extra() {
describe('configuration', () => {
beforeEach(async () => {
helper.createGetConfigurationStub();
helper.createExecuteCommandStub().withArgs('workbench.action.closeActiveEditor');
});

Expand All @@ -43,9 +42,7 @@ describe('MoveFileCommand', () => {

describe('move.closeOldTab set to true', () => {
beforeEach(async () => {
const keys: { [key: string]: boolean } = { 'move.closeOldTab': true };
const config = { get: (key: string) => keys[key] };
helper.createGetConfigurationStub().returns(config);
helper.createGetConfigurationStub({ 'move.closeOldTab': true });
});

it('moves a file and verifies that the tab of the file was closed', async () => {
Expand All @@ -56,9 +53,7 @@ describe('MoveFileCommand', () => {

describe('move.closeOldTab set to false', () => {
beforeEach(async () => {
const keys: { [key: string]: boolean } = { 'move.closeOldTab': false };
const config = { get: (key: string) => keys[key] };
helper.createGetConfigurationStub().returns(config);
helper.createGetConfigurationStub({ 'move.closeOldTab': false });
});

it('moves a file and verifies that the tab of the file was not closed', async () => {
Expand Down
Loading

0 comments on commit b638c84

Please sign in to comment.