DocsGenerator

Generators

Generators let you run shell commands to generate a list of Suggestions for a given Argument.

Generators are used to programatically generate suggestion objects. For instance, a generator can fetch a list of git remotes, grab all the folders in the current working directory, or even hit an API endpoint.

For a high level overview of Generators and their usage, see "Dynamic Suggestions".

Quick Summary

Generators let you run shell commands on the user's device to generate suggestions for arguments

Basic Generators

Run a shell command to provide dynamic suggestions.

See "Basic Generators" to understand the most common usage pattern.

Templates

Some generators, like the ones for filepaths and folders, are so common across CLI tools that it doesn't make sense for every spec to reimplement them from scratch.

See "Templates" to quickly provide rich suggestions for files and folders.

Contextual Generators

Provide suggestions that require context from other flags or options in the current edit buffer.

To handle this case, a Generator needs to run a script which incorporates text that the user has typed.

heroku addons:remove --app my-example-app |

For example, when completing this heroku command, Fig should only suggest addons that are associated with the specific app, my-example-app.

See "Contextual Generators" to learn how to run a script that incorporates text from the user's edit buffer.

Triggers & Filtering

Provide a new set of suggestions after the user types a certain character, like the / in a filepath, for instance.

See "Reimplementing the Filepath Generator" for more information of how to use triggers and filterTerm.

Caching & Debouncing

Suggestions are computed by an expensive function — a web request, for instance — and should be cached or debounced.

See "Making an HTTP Request from a Generator" for details on how to handle expensive functions.

Custom Generators

Write imperative code where suggestions must be recomputed on every keypress, to handle tools with non-standard parsing, like chmod.

See "Custom Generators" to learn more.

Properties

template

A template which is a single TemplateString or an array of TemplateStrings.

Optional Property:true

Declaration

template?: ("filepaths" | "folders" | "history" | "help") | ("filepaths" | "folders" | "history" | "help")[];

Discussion

Templates are generators prebuilt by Fig. Here are the three templates:

  • filepaths: show folders and filepaths. Allow autoexecute on filepaths
  • folders: show folders only. Allow autoexecute on folders
  • history: show suggestions for all items in history matching this pattern
  • help: show subcommands. Only includes the 'siblings' of the nearest 'parent' subcommand

Examples

  • cd uses the "folders" template
  • ls uses ["filepaths", "folders"]. Why both? Because if I ls a directory, we want to enable a user to autoexecute on this directory. If we just did "filepaths" they couldn't autoexecute.

filterTemplateSuggestions

A function to filter and modify suggestions returned by a template

Optional Property:true

Parameters

NameDescription
templateSuggestionsthe array of suggestion objects returned by the given template.

Return Value

An array of Suggestion objects.

Declaration

filterTemplateSuggestions?: (param?: Modify<Suggestion, {
            name?: string;
            context: {
                templateType: "filepaths";
            } | {
                templateType: "folders";
            } | {
                templateType: "help";
            } | ({
                templateType: "history";
            } & Partial<{
                currentWorkingDirectory: string;
                time: number;
                exitCode: number;
                shell: string;
            }>);
        }>[]) => Suggestion[];

Example

The python spec has an arg object which has a template for "filepaths". However, we don't want to suggest non .py files. Therefore, we take the output of the template, filter out all files that don't end in .py, keep all folders that end with / and return the list of suggetsions.


script

The script / shell command you wish to run on the user's device at their shell session's current working directory.

Optional Property:true

Declaration:script?: string | ((param?: string[]) => string);

Discussion

You can either specify

  1. a string to be executed (like ls or git branch)
  2. a function to generate the string to be executed. The function takes in an array of tokens of the user input and should output a string. You use a function when the script you run is dependent upon one of the tokens the user has already input (for instance an app name, a Kubernetes token etc.) After executing the script, the output will be passed to one of splitOn or postProcess for further processing to produce suggestion objects.

Example

git checkout <branch> takes one argument which is a git branch. Its arg object has a generator with a script: "git branch". The output of this shell command is then passed into the postProcess function to generate the final suggestions.


postProcess

Process the string output from the script prop and return a list of suggestions

Optional Property:true

Parameters

NameDescription
outThe output of the script that was executed on the user's device
tokensa tokenized array of what the user has typed

Return Value

An array of Suggestion objects.

Declaration

postProcess?: (out: string, tokens?: string[]) => Suggestion[];

splitOn

Syntactic sugar for postProcess function

Optional Property:true

Declaration:splitOn?: string;

Discussion

This takes in the text output of script, splits it on the string you provide here, and then automatically generates an array of suggestion objects for each item.

Example

Specify \n and Fig will split on new lines, and turn each line into a suggestion object with name prop equal to the value on the line.


trigger

A function run on every keystroke that determines whether Fig should invalidate its cached list of suggestions and instead regenerate its list of suggestions.

Optional Property:true

Parameters

NameDescription
newTokenThe new token that was just typed by the user e.g. "desktop/""
oldTokenThe old token that was there before e.g. "desktop"

Return Value

A boolean of whether or not we should regenerate suggestions

Declaration

trigger?: string | ((newToken: string, oldToken?: string) => boolean);

Discussion

A note on how Fig works: Suggestions vs Filtered Suggestions Suggestions: Whenever you type a space indicating the start of a new token, Fig will regenerate a new list of suggestions e.g. git[space] will generate a list of suggestions for every subcommand, option, and arg Filtered Suggestions: When you type within the same token (e.g. git c -> git ch), Fig takes the token you are currently typing in and uses it to filter over the list of suggestions you have cached. e.g. git c. The list of suggestions is the same as before, but the filtered suggestions are now commit, clean, clone, and checkout.

Why don't we recalculate suggestions on each keystroke?

  1. It's expensive
  2. We don't need to. The caching works nicely

So what does the trigger do? The trigger function is run on every keystroke and tells us whether or not we should invalidate the cache and regenerate a list of suggestions. The trigger function is almost always used with a custom generator and the getQueryTerm function to make the autocomplete experience really good (it's how suggestions for cd work) It is especially beneficial when you want to generate suggestions for an argument contained inside a single string that is not separated by a space.

What is important to remember? This function looks at the CHANGE in a token, not the current state of the token. If my token goes from desktop to desktop/, should I regenerate suggestions? Remember, users can paste text so theoretically any change is possible. It is totally valid for oldToken to be an empty string and newToken to be a 50 character string!

Default Value

false It means that the function returns false ie we do not regenerate suggestion on each keystroke and instead, keep our cached list of suggestions while the user is editing the current token.

Examples

  • chmod: If I type chmod u we should generate suggestions for u+x, u+r, u-w etc. Whereas if I typed chmod 7 we should generate suggestions for 755 or 777 etc. The suggestion we generate depends on the new information we have. The oldToken was an empty string, the new token could be a 7 or a u etc...

    All this function's job is to say whether or not we should generate new suggestions. It does not specify how to create these new suggestions. This is the job of the script or custom props. Annoyingly, you may have to implement some of the same parsing logic again. However, because this is javascript, just create a function so you don't have to repeat yourself :)

    Note: yes, we could have generate a list of suggestions at the start for every single permutation of 777 or u+x etc, however, there are so many and this is just not performant!

  • cd: Let's say a user has "cd desktop" currently typed then the user types a "/" so the changes to "cd ~/desktop/". The old token is "~/desktop", new token is "desktop/". This is a perfect time for us to generate a new list of suggestions. We previously had all the folders in my ~ directory as suggestions, but after the total number of / in the tokens changed, we shoudl trigger a new set of suggestions to be generated. This new set of suggestions should then generate suggestions for the desktop directory, not the ~ directory.


getQueryTerm

A function that takes the token that the user has typed and determines which part of it should be used to filter over all the suggestions.

Optional Property:true

Parameters

NameDescription
tokenThe full token the user is currently typing

Return Value

The query term that Fig will use to filter over suggestions

Declaration

getQueryTerm?: string | ((param?: string) => string);

Discussion

Read the note above on how triggers work. Triggers and query term may seem similar but are actually different.

The trigger function defines when to regenerate new suggestions. The query term defines what characters we should use to filter over these suggestions. The getQueryTerm function defines the queryTerm

Example

cd has a getQueryTerm function that takes the token the user has typed and returns everything after the last "/". if the user types cd ~/desktop/a, the list of suggestions will be all the folders on the user's desktop. We want to filter over these folders with the query term "a" not ~/desktop/a


custom

An async function that is similar to the function version of script, however, it gives you full control.

Optional Property:true

Parameters

NameDescription
tokensa tokenized array of what the user has typed
executeShellCommandan async function that allows you to execute a shell command on the user's system and get the output as a string.
shellContextan object containing a user's currentWorkingDirectory, currentProcess, and if relevant, the sshPrefix string that can be used if the user is in an SSH session.

Return Value

An array of suggestion objects

Declaration

custom?: (tokens: string[], executeShellCommand: (commandToExecute: string) => Promise<string>, shellContext?: {
            currentWorkingDirectory: string;
            currentProcess: string;
            sshPrefix: string;
        }) => Promise<Suggestion[]>;

Discussion

This function is effectively script and postProcess combined. It is very useful in combination with trigger and getQueryTerm to generate suggestions as the user is typing inside a token. Read the description of trigger for more.

Examples

  • In cd the custom function will combine the current working directory with everything up to the last "/" in the last token. It will then run ls at this path and generate a list of suggestions accordingly. e.g. if the user was currently in their home directory and typed "cd desktop/abcdef", then the custom function would return a list of directories at the ~/desktop directory if the user was currently in their home directory and typed "cd desktop/my_folder/second_folder/aaaaa", then the custom function would return a list of directories at the ~/desktop/my_folder/second_folder directory if the user was currently in their home directory and typed "cd /usr/bin/", then the custom function would return a list of directories at the /usr/bin/ directory
const generator: Fig.Generator = {
  custom: async (tokens, executeShellCommand) => {
    const out = await executeShellCommand("ls");
    return out.split("\n").map((elm) => ({ name: elm }));
  }
};

cache

Cache the response of generators for a specific period time and optionally by directory the commands were executed in.

Optional Property:true

Declaration

cache?: {
            /**
             * The time to live for the cache in milliseconds.
             * @example
             * 3600
             */
            ttl: number;
            /**
             * Whether the cache should be based on the directory the user was currently in or not
             * @defaultValue false
             */
            cacheByDirectory?: boolean;
        };

Discussion

For commands that take a long time to run, Fig gives you the option to cache their response. You can cache the response globally or just by the directory they were run in You just need to specify a ttl (time to live) for how long the cache will last (this is a number) You can also optionally turn on the ability to just cache by directory (cacheByDirectory: true)

Note: This may not work. We haven't touched this in a while as Fig has become much faster and there hasn't been a need

Example

The kubernetes spec makes use of this.