Skip to content
This repository has been archived by the owner on May 21, 2019. It is now read-only.

Unify subcommand autocomplete #941

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 27 additions & 50 deletions src/plugins/autocompletion_providers/Git.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as Git from "../../utils/Git";
import {
styles, Suggestion, longAndShortFlag, longFlag, mapSuggestions, unique,
contextIndependent, emptyProvider,
emptyProvider, SubcommandConfig, commandWithSubcommands,
} from "../autocompletion_utils/Common";
import * as Common from "../autocompletion_utils/Common";
import combine from "../autocompletion_utils/Combine";
import {PluginManager} from "../../PluginManager";
import {AutocompletionProvider, AutocompletionContext} from "../../Interfaces";
import {AutocompletionContext} from "../../Interfaces";
import {linedOutputOf, executeCommand} from "../../PTY";
import {find, sortBy} from "lodash";
import {find, sortBy, once} from "lodash";

const addOptions = combine([
mapSuggestions(longAndShortFlag("patch"), suggestion => suggestion.withDescription(
Expand Down Expand Up @@ -295,13 +295,7 @@ const notStagedFiles = unique(async(context: AutocompletionContext): Promise<Sug
}
});

interface GitCommandData {
name: string;
description: string;
provider: AutocompletionProvider;
}

const commandsData: GitCommandData[] = [
const commandsData: SubcommandConfig[] = [
{
name: "add",
description: "Add file contents to the index.",
Expand Down Expand Up @@ -506,7 +500,7 @@ const commandsData: GitCommandData[] = [
},
];

const commands = contextIndependent(async(): Promise<Suggestion[]> => {
const commands = once(async(): Promise<SubcommandConfig[]> => {
const text = await executeCommand("git", ["help", "-a"], process.env.HOME);
const matches = text.match(/ ([\-a-zA-Z0-9]+)/gm);

Expand All @@ -517,12 +511,11 @@ const commands = contextIndependent(async(): Promise<Suggestion[]> => {
const name = match.trim();
const data = find(commandsData, {name});

return new Suggestion({
value: name,
return {
name,
description: data ? data.description : "",
style: styles.command,
space: true,
});
provider: data ? data.provider : emptyProvider,
};
});

return sortBy(suggestions, suggestion => !suggestion.description);
Expand All @@ -531,41 +524,25 @@ const commands = contextIndependent(async(): Promise<Suggestion[]> => {
return [];
});

const aliases = contextIndependent(() => Git.aliases(process.env.HOME).then(
variables => variables.map(variable => new Suggestion({
value: variable.name,
synopsis: variable.value,
style: styles.alias,
space: true,
})),
));

const expandAlias = async(name: string): Promise<string> => {
const allAliases = await Git.aliases(process.env.HOME);
const alias = find(allAliases, {name});

return alias ? alias.value : name;
};
const aliases: () => Promise<SubcommandConfig[]> = once(async() => {
const aliasList = await Git.aliases(process.env.HOME);
return aliasList.map(({ name, value }) => {
let result: SubcommandConfig = {
name: name,
synopsis: value,
style: styles.alias,
};

const expandedAliasConfig = find(commandsData, data => data.name === value);
if (expandedAliasConfig && expandedAliasConfig.provider) {
result.provider = expandedAliasConfig.provider;
}

const allCommands = combine([aliases, commands]);
return result;
});
});

PluginManager.registerAutocompletionProvider("git", async context => {
if (context.argument.position === 1) {
return allCommands(context);
}

const firstArgument = context.argument.command.nthArgument(1);

if (!firstArgument) {
return [];
}

const name = await expandAlias(firstArgument.value);
const data = find(commandsData, {name});

if (data) {
return data.provider(context);
}

return [];
const allCommands = [...(await aliases()), ...(await commands())];
return commandWithSubcommands(allCommands)(context);
});
43 changes: 16 additions & 27 deletions src/plugins/autocompletion_providers/NPM.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Path from "path";
import {Suggestion, styles} from "../autocompletion_utils/Common";
import {Suggestion, styles, commandWithSubcommands} from "../autocompletion_utils/Common";
import {exists, readFile, mapObject} from "../../utils/Common";
import {PluginManager} from "../../PluginManager";
import {AutocompletionContext} from "../../Interfaces";

const npmCommandConfig = [
{
Expand Down Expand Up @@ -147,6 +148,19 @@ const npmCommandConfig = [
{
name: "run",
description: "Run arbitrary package scripts",
provider: async (context: AutocompletionContext) => {
const packageFilePath = Path.join(context.environment.pwd, "package.json");
if (await exists(packageFilePath)) {
const parsed = JSON.parse(await readFile(packageFilePath)).scripts || {};
return mapObject(parsed, (key: string, value: string) => new Suggestion({
value: key,
description: value,
style: styles.command,
}));
} else {
return [];
}
},
},
{
name: "search",
Expand Down Expand Up @@ -210,29 +224,4 @@ const npmCommandConfig = [
},
];

const npmCommand = npmCommandConfig.map(config => new Suggestion({value: config.name, description: config.description, style: styles.command}));

PluginManager.registerAutocompletionProvider("npm", async (context) => {
if (context.argument.position === 1) {
return npmCommand;
} else if (context.argument.position === 2) {
const firstArgument = context.argument.command.nthArgument(1);

if (firstArgument && firstArgument.value === "run") {
const packageFilePath = Path.join(context.environment.pwd, "package.json");

if (await exists(packageFilePath)) {
const parsed = JSON.parse(await readFile(packageFilePath)).scripts || {};
return mapObject(parsed, (key: string, value: string) => new Suggestion({value: key, description: value, style: styles.command}));
} else {
return [];
}
} else {
// TODO: handle npm sub commands other than "run" that can be
// further auto-completed
return [];
}
} else {
return [];
}
});
PluginManager.registerAutocompletionProvider("npm", commandWithSubcommands(npmCommandConfig));
6 changes: 2 additions & 4 deletions src/plugins/autocompletion_providers/Rails.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {styles, Suggestion} from "../autocompletion_utils/Common";
import {commandWithSubcommands} from "../autocompletion_utils/Common";
import {PluginManager} from "../../PluginManager";

const railsCommandConfig = [
Expand Down Expand Up @@ -36,6 +36,4 @@ const railsCommandConfig = [
},
];

const railsCommand = railsCommandConfig.map(config => new Suggestion({value: config.name, description: config.description, style: styles.command}));

PluginManager.registerAutocompletionProvider("rails", async() => railsCommand);
PluginManager.registerAutocompletionProvider("rails", commandWithSubcommands(railsCommandConfig));
51 changes: 22 additions & 29 deletions src/plugins/autocompletion_providers/Vagrant.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
import {PluginManager} from "../../PluginManager";
import {linedOutputOf} from "../../PTY";
import {styles, Suggestion, contextIndependent} from "../autocompletion_utils/Common";
import {executablesInPaths} from "../../utils/Common";
import {commandWithSubcommands, emptyProvider, SubcommandConfig} from "../autocompletion_utils/Common";
import {once} from "lodash";

const commands = contextIndependent(async() => {
return (await linedOutputOf("vagrant", ["list-commands"], process.env.HOME))
.map(line => {
const matches = line.match(/([\-a-zA-Z0-9]+) /);
const vargrantCommandConfig = once(async() => {
try {
return (await linedOutputOf("vagrant", ["list-commands"], process.env.HOME))
.map(line => {
const matches = line.match(/([\-a-zA-Z0-9]+) /);

if (matches) {
const name = matches[1];
const description = line.replace(matches[1], "").trim();
if (matches) {
const name = matches[1];
const description = line.replace(matches[1], "").trim();

return new Suggestion({
value: name,
description,
style: styles.command,
space: true,
});
}
})
.filter(suggestion => suggestion);
return {
name,
description,
provider: emptyProvider,
};
}
})
.filter(suggestion => suggestion) as SubcommandConfig[];
} catch (e) {
return [] as SubcommandConfig[];
}
});

PluginManager.registerAutocompletionProvider("vagrant", async (context) => {
const executables = await executablesInPaths(context.environment.path);

if (!executables.includes("vagrant")) {
return [];
}

if (context.argument.position === 1) {
return commands();
}

return [];
return commandWithSubcommands(await vargrantCommandConfig())(context);
});
31 changes: 31 additions & 0 deletions src/plugins/autocompletion_utils/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,34 @@ export const shortFlag = (char: string) => unique(async() => [new Suggestion({va
export const longFlag = (name: string) => unique(async() => [new Suggestion({value: `--${name}`, style: styles.option})]);

export const mapSuggestions = (provider: AutocompletionProvider, mapper: (suggestion: Suggestion) => Suggestion) => mk(async(context) => (await provider(context)).map(mapper));

export interface SubcommandConfig {
name: string;
description?: string;
synopsis?: string;
style?: Style;
provider?: AutocompletionProvider;
};

export const commandWithSubcommands = (subCommands: SubcommandConfig[]) => {
return async (context: AutocompletionContext) => {
if (context.argument.position === 1) {
return subCommands.map(({ name, description, synopsis, provider, style }) => new Suggestion({
value: name,
description,
synopsis,
style: style || styles.command,
space: provider !== undefined,
}));
} else if (context.argument.position === 2) {
const firstArgument = context.argument.command.nthArgument(1);
if (firstArgument) {
const subCommandConfig = subCommands.find(config => config.name === firstArgument.value);
if (subCommandConfig && subCommandConfig.provider) {
return await subCommandConfig.provider(context);
}
}
}
return [];
};
};