Skip to content

Commit

Permalink
fix(NewFileCommand): show folder selector
Browse files Browse the repository at this point in the history
This change addresses a behaviour were the folder selector (quick pick)
wans't show under all circumstances.

Now, provided "typeahead.enabled" is set to be true, the folder selector
will be shown if more than one director is present at the desired
location.
  • Loading branch information
sleistner committed Aug 23, 2019
1 parent 7c0ffb3 commit 38fb33f
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 69 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
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(/^!/, '')));
}
}
Loading

0 comments on commit 38fb33f

Please sign in to comment.