DocsGenerator

Generators

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

Generators are used to programmatically 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

Optional

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

template?:
  | ("filepaths" | "folders" | "history" | "help")
  | ("filepaths" | "folders" | "history" | "help")[]
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.

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

A function to filter and modify suggestions returned by a template

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[]
Examples

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 suggestions.

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

script

Optional

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

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

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.

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.

Set the execution timeout of the command specified in the script prop.

scriptTimeout?: number

postProcess

Optional

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

postProcess?: (out: string, tokens: string[]) => Suggestion[]
Parameters
NameDescription
outThe output of the script that was executed on the user's device
tokensa tokenized array of what the user has typed

splitOn

Optional

Syntactic sugar for postProcess function

splitOn?: string
Examples

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.

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.


trigger

Optional

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

trigger?:
  | string
  | ((newToken: string, oldToken: string) => boolean)
  | {
      /** Trigger on any change to the token */
      on: "change"
    }
  | {
      /** Trigger when the length of the token changes past a threshold */
      on: "threshold"
      length: number
    }
  | {
      /** Trigger when the index of a string changes */
      on: "match"
      string: string | string[]
    }
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 should 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.

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"
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!


getQueryTerm

Optional

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.

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

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

Parameters
NameDescription
tokenThe full token the user is currently typing
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


custom

Optional

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

custom?: (
  tokens: string[],
  executeShellCommand: (commandToExecute: string) => Promise<string>,
  shellContext: {
    currentWorkingDirectory: string
    currentProcess: string
    sshPrefix: string
  }
) => Promise<Suggestion[]>
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/myfolder/secondfolder/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 }));
  },
};
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.
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.


cache

Optional

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

cache?: (
  | {
      strategy?: "max-age"
      /**
       * The time to live for the cache in milliseconds.
       * @example
       * 3600
       */
      ttl: number
    }
  | {
      strategy: "stale-while-revalidate"
      /**
       * 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
}
Examples

The kubernetes spec makes use of this.

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. We currently have two cache strategies:

  • max-age (default): you just need to specify a ttl (time to live) for how long the cache will last (this is a number)
  • stale-while-revalidate: when cache becomes stale fig will return the stale data while fetching the updated one. This strategy also accepts a ttl to configure how long it takes for the cache to become stale. You can also optionally turn on the ability to just cache by directory (cacheByDirectory: true)