Skip to content

Commit db47ffc

Browse files
committed
refactor: drop tabtab and implement autocompletion using yargs callback
wip. still need to clean up the `autocompletion` commands for enabling/disabling autocompletion.
1 parent 57f816a commit db47ffc

File tree

7 files changed

+81
-171
lines changed

7 files changed

+81
-171
lines changed

lib/common/declarations.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,6 @@ interface IFutureDispatcher {
668668

669669
interface ICommandDispatcher {
670670
dispatchCommand(): Promise<void>;
671-
completeCommand(): Promise<boolean>;
672671
}
673672

674673
interface ICancellationService extends IDisposable {

lib/common/definitions/commands-service.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ interface ICommandsService {
99
commandName: string,
1010
commandArguments: string[]
1111
): Promise<boolean>;
12-
completeCommand(): Promise<boolean>;
1312
}
1413

1514
/**

lib/common/dispatchers.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ export class CommandDispatcher implements ICommandDispatcher {
7878
);
7979
}
8080

81-
public async completeCommand(): Promise<boolean> {
82-
return this.$commandsService.completeCommand();
83-
}
84-
8581
@hook("resolveCommand")
8682
private async resolveCommand(
8783
commandName: string,
@@ -113,10 +109,12 @@ export class CommandDispatcher implements ICommandDispatcher {
113109

114110
const spinner = this.$terminalSpinnerService.createSpinner();
115111
spinner.start("Checking for updates...");
116-
const nativescriptCliVersion = await this.$versionsService.getNativescriptCliVersion();
112+
const nativescriptCliVersion =
113+
await this.$versionsService.getNativescriptCliVersion();
117114
spinner.stop();
118115

119-
const packageManagerName = await this.$packageManager.getPackageManagerName();
116+
const packageManagerName =
117+
await this.$packageManager.getPackageManagerName();
120118
let updateCommand = "";
121119

122120
switch (packageManagerName) {

lib/common/services/commands-service.ts

Lines changed: 7 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ export class CommandsService implements ICommandsService {
6161
const command = this.$injector.resolveCommand(commandName);
6262
if (command) {
6363
if (!this.$staticConfig.disableAnalytics && !command.disableAnalytics) {
64-
const analyticsService = this.$injector.resolve<IAnalyticsService>(
65-
"analyticsService"
66-
); // This should be resolved here due to cyclic dependency
64+
const analyticsService =
65+
this.$injector.resolve<IAnalyticsService>("analyticsService"); // This should be resolved here due to cyclic dependency
6766
await analyticsService.checkConsent();
6867

6968
const beautifiedCommandName = this.beautifyCommandName(
@@ -242,9 +241,10 @@ export class CommandsService implements ICommandsService {
242241
defaultCommandDelimiter: CommandsDelimiters.DefaultHierarchicalCommand,
243242
};
244243

245-
const extensionData = await this.$extensibilityService.getExtensionNameWhereCommandIsRegistered(
246-
commandInfo
247-
);
244+
const extensionData =
245+
await this.$extensibilityService.getExtensionNameWhereCommandIsRegistered(
246+
commandInfo
247+
);
248248

249249
if (extensionData) {
250250
this.$logger.warn(extensionData.installationMessage);
@@ -405,145 +405,12 @@ export class CommandsService implements ICommandsService {
405405
}
406406
}
407407

408-
public async completeCommand(): Promise<boolean> {
409-
const tabtab = require("tabtab");
410-
411-
const completeCallback = (err: Error, data: any) => {
412-
if (err || !data) {
413-
return;
414-
}
415-
416-
const commands = this.$injector.getRegisteredCommandsNames(false);
417-
const splittedLine = data.line.split(/[ ]+/);
418-
const line = _.filter(splittedLine, (w) => w !== "");
419-
let commandName = <string>line[line.length - 2];
420-
421-
const childrenCommands = this.$injector.getChildrenCommandsNames(
422-
commandName
423-
);
424-
425-
if (data.last && _.startsWith(data.last, "--")) {
426-
return tabtab.log(_.keys(this.$options.options), data, "--");
427-
}
428-
429-
if (data.last && _.startsWith(data.last, "-")) {
430-
return tabtab.log(this.$options.shorthands, data, "-");
431-
}
432-
433-
if (data.words === 1) {
434-
const allCommands = this.allCommands({ includeDevCommands: false });
435-
return tabtab.log(allCommands, data);
436-
}
437-
438-
if (data.words >= 2) {
439-
// Hierarchical command
440-
if (data.words !== line.length) {
441-
commandName = `${line[data.words - 2]}|${line[data.words - 1]}`;
442-
} else {
443-
commandName = `${line[line.length - 1]}`;
444-
}
445-
}
446-
447-
const command = this.$injector.resolveCommand(commandName);
448-
if (command) {
449-
const completionData = command.completionData;
450-
if (completionData) {
451-
return tabtab.log(completionData, data);
452-
} else {
453-
return this.logChildrenCommandsNames(
454-
commandName,
455-
commands,
456-
tabtab,
457-
data
458-
);
459-
}
460-
} else if (childrenCommands) {
461-
let nonDefaultSubCommands = _.reject(
462-
childrenCommands,
463-
(children: string) => children[0] === "*"
464-
);
465-
let sanitizedChildrenCommands: string[] = [];
466-
467-
if (data.words !== line.length) {
468-
sanitizedChildrenCommands = nonDefaultSubCommands.map(
469-
(commandToMap: string) => {
470-
const pipePosition = commandToMap.indexOf("|");
471-
return commandToMap.substring(
472-
0,
473-
pipePosition !== -1 ? pipePosition : commandToMap.length
474-
);
475-
}
476-
);
477-
} else {
478-
nonDefaultSubCommands = nonDefaultSubCommands.filter(
479-
(commandNameToFilter: string) =>
480-
commandNameToFilter.indexOf("|") !== -1
481-
);
482-
sanitizedChildrenCommands = nonDefaultSubCommands.map(
483-
(commandToMap: string) => {
484-
const pipePosition = commandToMap.lastIndexOf("|");
485-
return commandToMap.substring(
486-
pipePosition !== -1 ? pipePosition + 1 : 0,
487-
commandToMap.length
488-
);
489-
}
490-
);
491-
}
492-
493-
return tabtab.log(sanitizedChildrenCommands, data);
494-
} else {
495-
return this.logChildrenCommandsNames(
496-
commandName,
497-
commands,
498-
tabtab,
499-
data
500-
);
501-
}
502-
};
503-
504-
// aliases to do autocompletion for
505-
const aliases = ["ns", "nsc", "tns", "nativescript"];
506-
507-
for await (const alias of aliases) {
508-
await tabtab.complete(alias, completeCallback);
509-
}
510-
511-
return true;
512-
}
513-
514408
private beautifyCommandName(commandName: string): string {
515409
if (commandName.indexOf("*") > 0) {
516-
return commandName.substr(0, commandName.indexOf("|"));
410+
return commandName.substring(0, commandName.indexOf("|"));
517411
}
518412

519413
return commandName;
520414
}
521-
522-
private logChildrenCommandsNames(
523-
commandName: string,
524-
commands: string[],
525-
tabtab: any,
526-
data: any
527-
) {
528-
const matchingCommands = commands
529-
.filter((commandToFilter: string) => {
530-
return (
531-
commandToFilter.indexOf(commandName + "|") !== -1 &&
532-
commandToFilter !== commandName
533-
);
534-
})
535-
.map((commandToMap: string) => {
536-
const commandResult = commandToMap.replace(commandName + "|", "");
537-
538-
return commandResult.substring(
539-
0,
540-
commandResult.indexOf("|") !== -1
541-
? commandResult.indexOf("|")
542-
: commandResult.length
543-
);
544-
});
545-
546-
return tabtab.log(matchingCommands, data);
547-
}
548415
}
549416
injector.register("commandsService", CommandsService);

lib/nativescript-cli.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
IExtensionData,
1818
} from "./common/definitions/extensibility";
1919
import { IInitializeService } from "./definitions/initialize-service";
20+
import { color } from "./color";
2021
installUncaughtExceptionListener(
2122
process.exit.bind(process, ErrorCodes.UNCAUGHT)
2223
);
@@ -29,23 +30,35 @@ process.on = (event: string, listener: any): any => {
2930
logger.trace(
3031
`Trying to handle SIGINT event. CLI overrides this behavior and does not allow handling SIGINT as this causes issues with Ctrl + C in terminal.`
3132
);
32-
const msg = "The stackTrace of the location trying to handle SIGINT is:";
33+
const msg = "The stackTrace of the location trying to handle SIGINT is";
3334
const stackTrace = new Error(msg).stack || "";
34-
logger.trace(stackTrace.replace(`Error: ${msg}`, msg));
35+
logger.trace(
36+
stackTrace.replace(
37+
`Error: ${msg}`,
38+
`${msg} (${color.yellow(
39+
"note:"
40+
)} this is not an error, just a stack-trace for debugging purposes):`
41+
)
42+
);
3543
} else {
3644
return originalProcessOn.apply(process, [event, listener]);
3745
}
3846
};
3947

4048
/* tslint:disable:no-floating-promises */
4149
(async () => {
50+
if (process.argv.includes("--get-yargs-completions")) {
51+
// This is a special case when we want to get the yargs completions as fast as possible...
52+
injector.resolve("$options");
53+
return;
54+
}
55+
4256
const config: Config.IConfig = injector.resolve("$config");
4357
const err: IErrors = injector.resolve("$errors");
4458
err.printCallStack = config.DEBUG;
4559

46-
const $initializeService = injector.resolve<IInitializeService>(
47-
"initializeService"
48-
);
60+
const $initializeService =
61+
injector.resolve<IInitializeService>("initializeService");
4962
await $initializeService.initialize();
5063

5164
const extensibilityService: IExtensibilityService = injector.resolve(
@@ -57,21 +70,16 @@ process.on = (event: string, listener: any): any => {
5770
logger.trace("Unable to load extensions. Error is: ", err);
5871
}
5972

60-
const commandDispatcher: ICommandDispatcher = injector.resolve(
61-
"commandDispatcher"
62-
);
63-
64-
const messages: IMessagesService = injector.resolve("$messagesService");
65-
messages.pathsToMessageJsonFiles = [
66-
/* Place client-specific json message file paths here */
67-
];
73+
const commandDispatcher: ICommandDispatcher =
74+
injector.resolve("commandDispatcher");
6875

69-
if (process.argv[2] === "completion") {
70-
await commandDispatcher.completeCommand();
71-
} else {
72-
await commandDispatcher.dispatchCommand();
73-
}
76+
// unused...
77+
// const messages: IMessagesService = injector.resolve("$messagesService");
78+
// messages.pathsToMessageJsonFiles = [
79+
// /* Place client-specific json message file paths here */
80+
// ];
7481

82+
await commandDispatcher.dispatchCommand();
7583
injector.dispose();
7684
})();
7785
/* tslint:enable:no-floating-promises */

lib/options.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,48 @@ export class Options {
368368
opts[this.getDashedOptionName(key)] = value;
369369
});
370370

371-
const parsed = yargs(hideBin(process.argv)).version(false).help(false);
371+
const parsed = yargs(hideBin(process.argv))
372+
.version(false)
373+
.help(false)
374+
.completion("completion_generate_script", async (current_, argv) => {
375+
const args: string[] = argv._.slice(1);
376+
const commands = injector
377+
.getRegisteredCommandsNames(false)
378+
.filter((c) => c != "/?"); // remove the /? command, looks weird... :D
379+
const currentDepth = args.length > 0 ? args.length - 1 : 0;
380+
const current = current_ ?? args[currentDepth] ?? "";
381+
// split all commands into their components ie. "device|list" => ["device", "list"]
382+
const matchGroups = commands.map((c) => c.split("|"));
383+
// find all commands that match the current depth and all the previous args
384+
const possibleMatches = matchGroups.filter((group) => {
385+
return group.slice(0, currentDepth).every((g, i) => {
386+
return g === args[i] || args[i].at(0) === "-";
387+
});
388+
});
389+
// filter out duplicates
390+
const completions = [
391+
...new Set(
392+
possibleMatches
393+
.map((match) => {
394+
return match[currentDepth];
395+
})
396+
.filter(Boolean)
397+
),
398+
];
399+
400+
// autocomplete long -- options
401+
if (current.startsWith("--")) {
402+
return this.optionNames.filter((o) => o !== "_").map((o) => `--${o}`);
403+
}
404+
405+
// autocomple short - options
406+
if (current.startsWith("-")) {
407+
return this.shorthands.map((o) => `-${o}`);
408+
}
409+
410+
// autocomplete matched completions
411+
return completions;
412+
});
372413
this.initialArgv = parsed.argv as any;
373414
this.argv = parsed.options(<any>opts).argv as any;
374415

@@ -378,9 +419,8 @@ export class Options {
378419
this.$settingsService.setSettings({
379420
profileDir: <string>this.argv.profileDir,
380421
});
381-
this.argv.profileDir = this.argv[
382-
"profile-dir"
383-
] = this.$settingsService.getProfileDir();
422+
this.argv.profileDir = this.argv["profile-dir"] =
423+
this.$settingsService.getProfileDir();
384424

385425
// if justlaunch is set, it takes precedence over the --watch flag and the default true value
386426
if (this.argv.justlaunch) {

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@
102102
"simple-plist": "1.4.0",
103103
"source-map": "0.7.4",
104104
"stringify-package": "1.0.1",
105-
"tabtab": "3.0.2",
106105
"tar": "6.1.13",
107106
"temp": "0.9.4",
108107
"ts-morph": "17.0.1",

0 commit comments

Comments
 (0)