Contextual Generators
Provide suggestions that require context from other flags or options in the current edit buffer.
Use a contextual generator when you want to run scripts dynamically based on parts of the command that the user has entered.
For example, Heroku requires the function takes a parameter, context
, which includes an array of strings broken down into components. The strings can then be used as values to pass to the script you'd like to return.
Note: The
context
array will group quoted components together. For example, when runningtouch "new file"
, the value ofcontext
is["touch", "new file"]
.See "Tokenizer" for more details on how the edit buffer is turned into the
context
array.
Reimplementing the filepath
Generator
Goals
Write a generator that reads the files and folders in a given directory, starting with the user's current directory.
Update the list of suggestions any time the user 'enters' a new folder.
Output the full filepath in order to use the Icon API to access the native macOS file icons.
The command we'll use looks like this:
ls -1AF /directory/to/show/suggestions/for/ | xargs -I '{}' echo $PWD/{}
Since we need to update the path that is passed in to ls
as the user navigates their file system, we can't define script
as a static string.
Instead, we need to run an updated version of script
whenever the user enters a new folder.
Triggering the generator and filtering suggestions
We can do this by creating a trigger
on the /
character. This will cause the generator to rerun it's script
whenever a slash is inserted or deleted from the edit buffer.
trigger: "/",
filterTerm: "/"
We also set filterTerm
equal to /
, so that Fig will only filter suggestions using the final component of the path.
For example, if the user has typed:
$ cd ~/Desktop/hello
We only want to use the string "hello" for filtering over the list of file & folder suggestions.
See "Triggers & Filtering" for more information.
Writing a contextual script
function
The script
function will first run when the user adds a space after cd
.
$ cd |
In this case the context
array will contain ["cd", ""]
. We'll start by storing the argument in variable path
and execute the command.
script: function(context) {
let path = context[context.length - 1]
return `ls -1AF ${path} | xargs -I '{}' echo $PWD/{}``
}
Invariant: When the
script
function is called, the last component ofcontext
will be the current argument.
While the first time that the script
function is run, the arg
will be empty, this isn't always the case.
$ cd ~/Desktop/|
If the user presses the Delete key now, the script
function will be triggered again and this time the context
array will contain ["cd", "~/Desktop"]
. If we use our current function, which blindly passes in the argument as the path, we will list the files and folders on the Desktop, rather than the ones in the user's home directory.
To fix this, we need to check if the path
contains a slash and, if it does, drop the last component of the path. Otherwise, we should just list the files and folders in the current directory.
if (path.includes('/')) {
let components = path.split('/')
components.pop()
path = components.join('/')
} else {
path = '.'
}
Finally, let's escape any spaces that appear in the path.
path = path.split(" ").join("\\ ")
Here is the final result:
script: function(context) {
let path = context[context.length - 1]
if (path.includes('/')) {
let components = path.split('/')
components.pop()
path = components.join('/')
} else {
path = '.'
}
// escape spaces in path
path = path.split(" ").join("\\ ")
// output full filepath for each item in `path` folder
return `ls -1AF ${path} | xargs -I '{}' echo $PWD/{}`
}
Postprocessing the list of files and folders
The last step is writing a function to take the output of ls
and convert it to an array of Suggestion objects.
postProcess: function(files) {
return files.split('\n').map(path => {
let components = path.split('/')
let filename = components[components.length - 1]
let isFolder = path.endsWith('/')
if (isFolder) {
filename = components[components.length - 2] + '/'
}
return {
name: filename,
icon: 'fig://' + path,
description: isFolder ? 'folder' : 'file'
}
})
In this postProcess
function, we split the raw output on new lines and then map over it, making slight tweaks to the Suggestion object depending on whether the item is a file or a folder.
To set the icon, we pass the full path of the file to the fig://
URL scheme, which returns the native filesystem icon associated with it.
See " Icon API" for more details.
Full Code
const filepathGenerator: Fig.Generator = {
script: function(context) {
let path = context[context.length - 1]
if (path.includes('/')) {
let components = path.split('/')
components.pop()
path = components.join('/')
} else {
path = '.'
}
// escape spaces in path
path = path.split(" ").join("\\")
// output full filepath for each item in `path` folder
return `ls -1AF ${path} | xargs -I '{}' echo $PWD/{} `
},
postProcess: function(files) {
return files.split('\n').map(path => {
let components = path.split('/')
let filename = components[components.length - 1]
let isFolder = path.endsWith('/')
if (isFolder) {
filename = components[components.length - 2] + '/'
}
return {
name: filename,
icon: 'fig://' + path,
description: isFolder ? 'folder' : 'file'
}
})
},
trigger: "/",
filterTerm: "/"
}