hollow.ui.workspace
June 25, 2026 · View on GitHub
The workspace switcher is a high-level widget built on
hollow.ui.select and
hollow.workspace.
It merges currently open workspaces, recently used ones, and
configured discovery sources.
For the conceptual model see
Panes, tabs, workspaces → Workspaces.
For the LuaLS schema see
types/hollow.lua (HollowUiWorkspaceItem,
HollowUiWorkspaceSource, HollowUiWorkspaceSwitcherOptions).
Functions
hollow.ui.workspace.configure(opts?) -- persisted picker settings
hollow.ui.workspace.clear_cache() -- reset discovery cache
hollow.ui.workspace.known_workspaces(force?) -- items from known sources
hollow.ui.workspace.items(force?) -- open + known merged
hollow.ui.workspace.open_switcher(opts?) -- show the picker
hollow.ui.workspace.switcher(opts?) -- alias of open_switcher
hollow.ui.workspace.topbar_button(opts?) -- clickable bar node
hollow.ui.workspace.create(opts?) -- create a new workspace
hollow.ui.workspace.rename(workspace?, opts?) -- rename
hollow.ui.workspace.close(workspace?) -- close
hollow.ui.workspace.open(opts) -- open an ad hoc item
Workspace item shape
HollowUiWorkspaceItem = {
id = string,
name = string,
cwd = string | nil,
domain = string | nil,
source = "open" | "user" | "local" | "wsl" | "ssh",
is_active = boolean,
is_open = boolean,
open_index = integer | nil,
last_opened_at = integer | nil,
}
String items passed to open are normalized into { name = ... }
entries.
Configuring the picker
hollow.ui.workspace.configure({
prompt = "Workspaces",
width = 96,
height = nil,
max_height = 18,
backdrop = true,
chrome = { bg = "#1f1f28", border = "#3a3a52", radius = 6 },
theme = { ... },
cache_ttl_ms = 5000,
project_roots = { "C:/code" },
known_workspaces = function() return { ... } end,
sources = {
{ resolver = "local", domain = "pwsh", roots = { "C:/code" } },
{ resolver = "wsl", domain = "wsl", roots = { "/home/me/projects" } },
{ resolver = "ssh", domain = "tower", roots = { "/home/me/projects" } },
},
format_item = function(workspace) return { ... } end,
filter_item = function(workspace) return workspace.name ~= "scratch" end,
status_column_width = 2,
name_column_width = 24,
column_gap = 2,
rename_key = "<C-r>", rename_desc = "rename",
close_key = "<C-w>", close_desc = "close",
create_key = "<C-n>", create_desc = "create",
})
Changing known_workspaces, sources, or project_roots invalidates
the cache. You can also call hollow.ui.workspace.clear_cache()
directly.
Sources
Each source declares how its items are discovered:
{
resolver = "local" | "wsl" | "ssh",
name = "Ubuntu", -- optional
domain = "wsl", -- the domain new panes open in
roots = { "/home/me/projects" },
items = function() return { ... } end, -- optional, merged with roots
cwd_resolver = "wsl_unc" | function(cwd, item, source) ... end,
default = false,
}
| Resolver | Behavior |
|---|---|
local | Scans roots with hollow.read_dir(...) |
wsl | Runs wsl.exe and lists child directories |
ssh | Uses hollow.term.run_domain_process(...) to list directories on the SSH domain |
cwd_resolver = "wsl_unc" is useful when picker items come from
Windows UNC paths like \\wsl$\Ubuntu\home\me\Projects but the
launched shell should cd to the Linux-side path.
Discovery and caching
- Open workspaces are always live
- Discovered workspaces are cached for
cache_ttl_ms force_refresh = truebypasses the cache onceclear_cache()resets the cached discovery results
Recent activity timestamps are updated from terminal events (tab activation, cwd change, title change, tab close).
Opening and switching
open_switcher() shows the picker. Default actions:
- Primary action: switch to / open the selected workspace
rename_key: rename the active workspace (default<C-r>)close_key: close the active workspace (default<C-w>)create_key: create a new workspace (default<C-n>)
Selection behaviour:
- Already-open workspaces: switch to them
- Known workspaces: open with their
cwdanddomain source = "ssh"workspaces: open in the target domain and send acdcommand- User-defined items with a
cwdresendcdwhen re-activated
open(opts) is the programmatic entry point. Pass an item directly
or specify source = "..." plus an item payload to resolve through
a configured source first.
Top-bar button
Renders a clickable workspace badge for the top bar. By default shows the current workspace name (bold) with an index/count suffix (dimmed), colorized by workspace name.
hollow.ui.workspace.topbar_button(opts?)
Options:
{
text = "custom text", -- plain text override (skips prefix/suffix)
prefix = " ", -- before the name (default " ")
suffix = " 2/4", -- after the name (default " index/count")
style = { fg = "...", bg = "..." },
colorize = true, -- pick bg/fg from workspace name hash
id = "my-button",
switcher = { ... }, -- passed to open_switcher on click
}
When text is set, returns a single span node. Otherwise returns two
spans (name bold, suffix dimmed). Both have on_click wired to
open_switcher.
With bar.workspace (default conf behavior):
workspace = {
format = function(ws)
return hollow.ui.workspace.topbar_button({ colorize = true })
end,
}
Standalone in a custom bar:
hollow.ui.topbar.mount(hollow.ui.topbar.new({
render = function()
return {
hollow.ui.workspace.topbar_button(),
hollow.ui.spacer(),
hollow.ui.bar.time("%H:%M"),
}
end,
}))
Examples
WSL source with UNC paths:
hollow.ui.workspace.configure({
sources = {
{
name = "Ubuntu",
resolver = "local", -- not "wsl" if roots are UNC paths
domain = "wsl",
cwd_resolver = "wsl_unc",
roots = {
"\\\\wsl$\\Ubuntu\\home\\me\\Projects",
},
},
},
})
Custom picker rows:
hollow.ui.workspace.configure({
format_item = function(ws)
return {
hollow.ui.span(ws.is_active and "* " or " "),
hollow.ui.span(ws.name, { bold = ws.is_active }),
hollow.ui.span(ws.cwd and (" " .. ws.cwd) or "", { fg = "#727169" }),
}
end,
})
Hide items:
hollow.ui.workspace.configure({
filter_item = function(ws) return ws.name ~= "scratch" end,
})
See also
- Custom UI
- WSL → WSL workflow patterns
hollow.workspace— workspace bootstrap