Using Alfred to Quickly Open Workspaces


Background
I use VS Code for almost all of my development. It allows me to save folders and their custom settings to workspace files.
Alfred is a macOS program which serves as a launcher, clipboard manager, finder, and much more. In this case, I want to use Alfred as a way to quickly, interactively choose a VS Code workspace to open.
Required Structure
Alfred’s Script Filters allow us to search a dynamic list for some item, and then do something with that item. The documentation then links to the Script Filter JSON format. We’re going to create a script (binary) that outputs all discovered workspaces in the required format.
Code
We can use basic commands to discover all workspaces we want to include:
1find ~/code -type file -maxdepth 1 -name '*.code-workspace'
This gives us all workspace files in the ~/code directory. From these, we can easily create the JSON object required by Alfred.
The equivalent command from go is:
1filepath.Glob(fmt.Sprintf("%s/*%s", filepath.Join(home, codeDir), workspaceFileType))
where home is our home directory, and codeDir is the folder in our home directory that contains our workspaces. Now that I’ve found all our workspace files, I just need to create JSON objects from them that Alfred will consume.
We’ll define a basic struct to represent that object:
1type alfredList struct {
2 Items []alfredItem `json:"items"` // All the items to choose from
3}
4type alfredItem struct {
5 UID string `json:"uid"` // ID, just the basename
6 Type string `json:"type"` // Type of item, always 'file'
7 Title string `json:"title"` // Title to display - friendly name
8 Subtitle string `json:"subtitle"` // Subtitle to display - short filepath
9 File string `json:"arg"` // Filepath to open if the item is selected
10 Autocomplete string `json:"autocomplete"` // String to use for autocomplete
11}
We can definitely get fancier with our objects, but for now we’ll keep it simple. Our function will create an alfredItem given the filepath to a workspace file:
1// newAlfredItem creates an alfredItem given the path of a workspace file
2func newAlfredItem(workspaceFile string) alfredItem {
3 // Remove leading path and file extension
4 baseName := strings.Replace(filepath.Base(workspaceFile), workspaceFileType, "", 1)
5
6 return alfredItem{
7 UID: baseName,
8 Type: "file",
9 Title: baseName,
10 Subtitle: filepath.Join("~", codeDir, baseName), // Use '~' instead of $HOME
11 File: workspaceFile,
12 Autocomplete: strings.ReplaceAll(baseName, "-", " ") // Split dash-separated words
13 }
14}
So we want to feed all discovered workflow files into our newAlfredItem method, and then print those in the required format. With basic error handling, we end up with this:
1matches, err := filepath.Glob(fmt.Sprintf("%s/*%s", filepath.Join(home, codeDir), workspaceFileType))
2if err != nil {
3 log.Fatal(err)
4}
5
6list := alfredList{}
7for _, m := range matches {
8 list.Items = append(list.Items, newAlfredItem(m))
9}
10
11output, err := json.Marshal(list)
12if err != nil {
13 log.Fatal(err)
14}
15fmt.Println(string(output))
Using From Alfred
Create a new blank workflow. In the new workflow, add a Script Filter by selecting Inputs -> Script Filter. We need to give it some keyword that will bring up the list when we type it. I used ‘vc’ because nothing else was using it.
To get the result of our program into the list of items, we’ll just compile our Go program and run the binary from our workflow.

When we run the workflow by entering the keyword vc, we see a list of the discovered workspaces:

By hitting Enter on a single workspace, we’ll open it in VS Code.
Improvements
Unless we’re adding a lot of workspaces each day, we’ll be fine getting our list from a static-ish JSON file. In the past, I’ve used Lingon to create a job that periodically rediscovers each workspace file and saves them to a JSON file.
