|
| Mode | Shortcut | Description |
|---|---|---|
| Popup | Ctrl+Alt+F / Shift Shift |
Floating modal — opens over the editor, closes with Esc (keeps open after selection when spyglass.closeOnSelect is false) |
| Sidebar | Ctrl+Alt+E |
Persistent panel in the Activity Bar — stays open as you work |
Keep Spyglass always visible — set spyglass.openExternalWindow: true so the popup opens detached in a side editor group and stays open after selecting a result, then drag the panel's tab out of the VS Code window onto a second monitor to get a standalone window.
The sidebar adapts to its width automatically:
- Narrow (< 420 px) — results only, no preview
- Medium (420–599 px) — preview panel stacked below results
- Wide (≥ 600 px) — preview panel beside results (classic split)
In a narrow sidebar (< 420 px) the search box gets the room: regex, case, whole word and replace move into the ⋯ menu, with a dot on ⋯ while any of them is on. Tabs that do not fit scroll sideways (mouse wheel or Tab), with a › / ‹ at the edge.
Both modes share the same features, keyboard shortcuts, and state.
⚡ Actions
- Find & Replace with preview —
Alt+Rto enable replace mode; "Replace all" shows a diff overlay (before/after per line per file) before applying; Apply or Cancel; uses VS Code WorkspaceEdit (supports undo) - Copy path — copy the absolute path of the selected result
- Reveal in Explorer — click the preview header to locate the file
- Open in split — open any result beside the current editor
- Pre-fill from selection — select text, open Spyglass → query is pre-filled
- Zero dependencies — ripgrep is bundled, nothing to install
🚀 Usage
Opening Spyglass
| Action | Shortcut |
|---|---|
| Open popup (floating modal) | Ctrl+Alt+F |
| Open popup (double-tap Shift, JetBrains-style) | Shift Shift |
| Focus sidebar panel | Ctrl+Alt+E |
VSCode Vim users — bind
<Space>fas your leader shortcut. See Vim setup below.
⌨️ Keyboard shortcuts
| Action | Shortcut |
|---|---|
| Navigate results | ↑ / ↓ |
| Navigate results (Telescope / Emacs style) | Ctrl+J / Ctrl+K, Ctrl+N / Ctrl+P |
| Page down / up | PageDown / PageUp |
| First / last result | Ctrl+Home / Ctrl+End |
| Scroll the preview down / up | Ctrl+D / Ctrl+U |
| Open selected file | Enter |
| Open in split editor | Ctrl+Enter |
| Switch scope | Tab |
| Previous scope | Shift+Tab |
| Close | Escape |
| Toggle regex | Shift+Alt+R |
| Toggle case sensitive | Alt+C |
| Toggle whole word | Alt+W |
| Group results by file | Alt+L |
| Sort results (cycle) | Alt+S |
| Include filter row | Alt+I |
| Include ignored & hidden files | Alt+H |
| Multiline search (regex) | Alt+M |
| Toggle preview panel | Shift+Alt+P |
| Toggle replace mode | Alt+R |
| Focus replace input (in replace mode) | Tab |
| History — previous query | Ctrl+↑ |
| History — next query | Ctrl+↓ |
| Save search (bookmark) | Alt+B |
| Copy path | Alt+Y |
| Pin / Unpin file | Alt+P |
| Multi-select toggle | Ctrl+Space / Ctrl+Click |
| Select all results | Ctrl+A |
| Open all selected | Shift+Enter |
| Reveal in Explorer | click the preview header |
| Refresh git changed files | F5 (in Git scope) |
🗺️ Search Scopes
| Scope | Description |
|---|---|
| Project | Full-text search across all files in the workspace |
| Open Files | Full-text search only within currently open editor tabs |
| Files | Fuzzy search by filename across the whole project |
| Recent | Recently opened files, ranked by frecency (frequency + recency); pinned files first |
| Dir | Full-text search within the directory of the active file |
| Symbols | Workspace symbol search via LSP (requires a language extension) |
| Git | All files with uncommitted changes — modified, added, untracked, deleted, renamed |
| Doc | Document symbols for the currently active file via LSP |
| Refs | All references to the symbol under the cursor at the time Spyglass was opened |
Switch between scopes with Tab while Spyglass is open.
🧭 Commands
Besides the general Spyglass: Open Search Popup, every scope has its own command in the Command Palette. Each one opens Spyglass straight in that scope (or switches an already open popup to it) and loads its list right away where that makes sense (Recent, Git, Doc, Refs):
| Command | Scope |
|---|---|
Spyglass: Find in Project |
Project — full-text search |
Spyglass: Find Files |
Files — fuzzy search by file name |
Spyglass: Find in Open Files |
Open Files — full-text search in open tabs |
Spyglass: Find Recent Files |
Recent |
Spyglass: Find in Current Directory |
Dir |
Spyglass: Find Symbols in Workspace |
Symbols |
Spyglass: Find Git Changes |
Git |
Spyglass: Find Symbols in Document |
Doc |
Spyglass: Find References |
Refs |
Spyglass: Find Commands |
Commands — run a VS Code command (see Run commands) |
Spyglass: Find in Folder |
Dir, on a folder chosen in the Explorer (right-click a folder → Spyglass: Find in Folder) |
Spyglass: Resume Last Search |
Reopens the popup on the last query, in its scope, at the result you had selected (like :Telescope resume) |
Two of them are also in context menus: right-click a folder in the Explorer for Find in Folder, and right-click selected text in an editor for Find in Project (the selection becomes the query). Spyglass: What's New opens the changelog. None of them has a default shortcut, so nothing you already use is taken over. Bind the ones you want in keybindings.json (Ctrl+Shift+P → Open Keyboard Shortcuts (JSON)), for example to get Quick Open-, Go to Symbol- and Find in Files-style keys:
[
{ "key": "ctrl+p", "command": "spyglass.findFiles" },
{ "key": "ctrl+t", "command": "spyglass.findSymbols" },
{ "key": "ctrl+shift+o", "command": "spyglass.findDocumentSymbols" },
{ "key": "ctrl+shift+f", "command": "spyglass.findInProject" }
]
The general command takes the scope as an argument too, which is handy if you prefer one command id everywhere:
{ "key": "ctrl+p", "command": "spyglass.open", "args": { "scope": "files" } }
Valid scopes are project, openFiles, files, recent, here, symbols, git, doc and refs; an unknown value is ignored and Spyglass opens as usual.
A scope chosen by a command is not remembered: the plain Ctrl+Alt+F / Shift Shift shortcut still reopens in the scope you last picked yourself with the tabs.
⚡ Run commands
Type > at the start of the query in Files or Recent — or run Spyglass: Find Commands — to list VS Code commands, as in the Command Palette:
- Each row reads Category: Title with its default keybinding; the commands you ran from Spyglass recently come first.
Enterruns the selected command on the editor Spyglass was opened from (the popup closes first; the sidebar hands focus to the editor).- The preview pane shows the command's id, where it comes from and its key — the id is what you need to bind your own key.
- The last row, Show all commands for '…', opens VS Code's own Command Palette with the same text, for anything Spyglass does not list.
- Delete the
>to go back to the file list. A Commands tab shows while the list is open.
Commands come from your extensions (their manifests) plus about 100 common commands of VS Code itself. VS Code does not tell extensions about keys you have re-bound, so the keys shown are the defaults. Want Spyglass to be your palette? { "key": "ctrl+shift+p", "command": "spyglass.findCommands" }.
🎯 Go to line & symbol
In the Files and Recent lists the query understands the same shortcuts as VS Code's Quick Open (Ctrl+P):
| Type | What happens |
|---|---|
util.ts:42 |
Filters the files by util.ts; the preview shows line 42 and Enter opens the file there (also in the Git list) |
util.ts:42:7 |
The same, with the cursor at column 7 |
:42 |
Line 42 in the file you are editing: one result, previewed at that line |
@ |
Switches to the Doc scope — the symbols of the current file — and keeps the @ in the box; @init filters them |
Delete the @ and you are back in the list you came from. A line past the end of the file goes to its last line. In the text search scopes (Project, Open Files, Dir) nothing changes: std::vector or @Override are searched as text.
🔄 Find & Replace
- Open Spyglass and type your search query
- Press
Alt+R(or click⇄) to enable replace mode - Type the replacement text in the second field (
Tabmoves focus from query to replace input) - Optionally tune case-sensitive / whole-word / glob filter
- Click Replace all — a diff preview overlay appears showing every changed line (before in red, after in green) grouped by file
- Click Apply to apply all changes, or Cancel to abort
Changes are applied via VS Code's WorkspaceEdit API and are fully undoable (Ctrl+Z).
👁️ Preview Panel
The right-side preview shows the file around the matched line with syntax highlighting. Lines modified since the last git commit are marked with a blue indicator in the gutter.
- Toggle with
Shift+Alt+Por the⊡button - Click the preview header to Reveal in Explorer
⚙️ Settings
| Setting | Default | Description |
|---|---|---|
spyglass.defaultScope |
project |
Scope on open: project openFiles files recent here symbols git doc refs |
spyglass.maxResults |
200 |
Maximum number of results to display (1–5000) |
spyglass.maxMatchesPerFile |
10 |
Maximum number of matches shown per file in text search |
spyglass.maxFileSize |
"1M" |
Files larger than this are skipped by text search (500K, 1M, 10M, 1G) |
spyglass.exclude |
[".git","node_modules","out","dist","*.lock"] |
Glob patterns excluded from search and file listing (lifted while Include ignored & hidden files is on, except .git) |
spyglass.ripgrepPath |
"" |
Path to your own rg binary. Leave empty to use the ripgrep that ships with VS Code |
spyglass.showWhatsNew |
true |
Show a notification linking to the changelog after an update to a new minor or major version |
spyglass.openOnSide |
false |
Open the popup in a side column instead of the active editor column |
spyglass.closeOnSelect |
true |
Close the popup after opening a result. Disable to keep it open and open multiple results from one search |
spyglass.openExternalWindow |
false |
Like "openOnSide", but also keeps the popup open after selecting a result — ideal for keeping Spyglass on a second monitor (drag the panel's tab out of the window to make it a standalone window) |
spyglass.keybindings.navigateDown |
ArrowDown |
Navigate down in results |
spyglass.keybindings.navigateUp |
ArrowUp |
Navigate up in results |
spyglass.keybindings.open |
Enter |
Open selected result |
spyglass.keybindings.close |
Escape |
Close Spyglass |
spyglass.keybindings.toggleRegex |
shift+alt+r |
Toggle regex mode |
spyglass.keybindings.togglePreview |
shift+alt+p |
Toggle preview panel |
🎹 Customizing Keybindings
Change the open shortcut
Open Keyboard Shortcuts (Ctrl+K Ctrl+S), search for Spyglass: Open Search Popup, Spyglass: Focus Sidebar Panel or one of the per-scope commands and assign your preferred keys.
Or edit keybindings.json directly (Ctrl+Shift+P → Open Keyboard Shortcuts (JSON)):
[
{
"key": "ctrl+alt+f",
"command": "spyglass.open",
"when": "!inputFocus || editorTextFocus"
},
{
"key": "ctrl+alt+e",
"command": "spyglass.focusSidebar"
}
]
Change shortcuts inside the panel
Add to your settings.json:
{
"spyglass.keybindings.navigateDown": "j",
"spyglass.keybindings.navigateUp": "k",
"spyglass.keybindings.toggleRegex": "ctrl+r",
"spyglass.keybindings.togglePreview": "ctrl+p"
}
🟢 Vim Setup
vscodevim (VSCode Vim extension)
VSCode Vim intercepts Space before VS Code sees it, so the built-in Space f shortcut won't work. Configure it through VSCode Vim instead — add to your settings.json:
{
"vim.normalModeKeyBindingsNonRecursive": [
{
"before": ["<Space>", "f"],
"commands": ["spyglass.open"]
}
]
}
vscode-neovim
The built-in Space f binding works out of the box in normal mode — no extra configuration needed.
To disable the default Ctrl+Alt+F binding for either setup:
[
{
"key": "ctrl+alt+f",
"command": "-spyglass.open"
}
]
🌐 Multi-root Workspaces
Spyglass works across all workspace folders simultaneously. Results from multiple folders are prefixed with the folder name so you always know where a match comes from:
backend/src/server.ts
frontend/src/App.tsx
All scopes — text search, file listing, replace, and git status badges — cover every folder in the workspace.
🔍 Inline Glob Filter
Append a glob pattern to any query to narrow the search without leaving the input field:
| Query | Effect |
|---|---|
useState *.tsx |
Search for useState only in .tsx files |
TODO !*.test.ts |
Search for TODO, excluding test files |
error *.ts !*.d.ts |
Multiple globs combined |
Patterns starting with * are treated as include globs, patterns starting with ! as excludes. Everything else is the search query.
🙈 Ignored & hidden files
By default Spyglass searches what ripgrep would: it skips files listed in .gitignore / .ignore, hidden files (dotfiles such as .env, folders such as .github) and anything matching spyglass.exclude (node_modules, dist, out, *.lock …). Turn on Include ignored and hidden files — in the ⋯ menu, or Alt+H — to search those too. When it is on, the ⋯ button shows a dot so you do not forget.
- It applies to Project, Open Files, Dir and Files, and to Replace all, so the preview always covers exactly the files the search shows.
- It lifts
spyglass.excludeas well, because otherwisedist/andnode_modules/would stay hidden. The.gitfolder is always skipped. - The choice is remembered with the other toolbar toggles. It can make searches slower and noisier in big repositories (
node_modules, build output), so turn it off when you are done.
↵ Multiline search
Turn on Multiline search — in the ⋯ menu, or Alt+M — to let a pattern match across line breaks. It is always a regular expression (you cannot type a line break into a plain string), whatever the regex toggle says. When it is on, the ⋯ button shows a dot.
| Query | Finds |
|---|---|
import .*;\nimport |
two import lines in a row |
\{\n\s*return |
a block that opens and returns on the next line |
(?s)<div>.*?</div> |
anything between the tags, across any number of lines |
.does not match a line break unless you start the pattern with(?s);\n,\sand[\s\S]always do.- A result that spans several lines is shown as its first line with a
+Nbadge (N more lines);Enteropens the file at that first line. - Replace all is not available in multiline mode, because replacing works line by line and a wrong multi-line replacement is easy to miss. Turning multiline on switches replace mode off.
- It applies to Project, Open Files and Dir. Searching is slower on big files, since ripgrep reads each file whole.
📏 Search limits
To stay fast on large projects, text search caps how much it returns. All three caps are settings, so you can raise them when something you expect is missing:
| Symptom | Setting | Default |
|---|---|---|
The header shows 200+ results and asks you to narrow the query |
spyglass.maxResults (1–5000) |
200 |
| A file clearly contains more occurrences than Spyglass lists | spyglass.maxMatchesPerFile |
10 |
| A match in a very large file (generated code, bundles, dumps) never shows up | spyglass.maxFileSize |
"1M" |
{
"spyglass.maxResults": 1000,
"spyglass.maxMatchesPerFile": 50,
"spyglass.maxFileSize": "5M"
}
Higher values mean more work for ripgrep and a longer list to render, so raise them only as far as you need. Invalid values fall back to the defaults, and spyglass.maxResults above 5000 is capped at 5000.
📋 Requirements
- VS Code
^1.85.0 - No additional dependencies — ripgrep is bundled automatically
- Git (optional) — required for change indicators in the preview panel
- A language server extension (optional) — required for the Symbols scope
Privacy
Spyglass collects no data. All processing happens locally on your machine:
- No network requests are made while searching (webview CSP is
default-src 'none'). The one exception: if no ripgrep binary can be found (neither the one that ships with VS Code nor the bundled@vscode/ripgrep), Spyglass downloads the official ripgrep release from GitHub once, into its extension storage. Setspyglass.ripgrepPathto point at your ownrgto avoid that - No telemetry, analytics, or crash reporting
- Search history is stored locally in VS Code's
workspaceStateand never leaves your machine - Dependencies (
@vscode/ripgrep,shiki) run fully locally
🛠️ Development
git clone https://github.com/garroter/spyglass
cd spyglass
npm install
npm run compile # compile TypeScript
npm run watch # watch mode
npm test # run unit tests (vitest)
npm run test:ui # run UI tests (Playwright + headless Chromium)
npm run test:ui rebuilds the webview bundle and drives the real UI against the real search backend,
with only the VS Code API mocked. The first time, install the browser with npx playwright install chromium
(or set SPYGLASS_CHROMIUM to an existing Chromium/Chrome binary).
Press F5 in VS Code to launch an Extension Development Host.
Continuous integration and releases
GitHub Actions runs lint, type-checks, the unit tests and the UI tests on every push to main and on every pull request (.github/workflows/ci.yml), and builds the .vsix. To release, bump version in package.json, give CHANGELOG.md a ## [x.y.z] section, and push the tag vx.y.z: the release workflow checks the tag and the changelog, runs the same checks and publishes a GitHub Release with the .vsix that passed them, then publishes the same .vsix to the VS Code Marketplace and Open VSX (secrets VSCE_PAT and OVSX_PAT; a store without its secret is skipped).
Project structure
src/
extension.ts — activation, command registration
FinderPanel.ts — popup: webview panel lifecycle, opening files
SpyglassSidebarProvider.ts — sidebar view provider
SpyglassController.ts — state, message handling and searches shared by the popup and the sidebar
scopeCommands.ts — the per-scope "Find …" commands and scope-argument parsing
webviewHtml.ts — HTML template for the webview (pure function)
ripgrep.ts — ripgrep search backend
gitUtils.ts — git status and diff parsing
symbolSearch.ts — LSP workspace symbol search
themeLoader.ts — loads the active VS Code theme for Shiki
workspaceUtils.ts — path helpers (cwdForFile, makeRelative)
webviewUtils.ts — pure functions shared with tests (fuzzyScore, parseQueryInput)
webview/ — webview UI (TypeScript, bundled with esbuild)
main.ts — entry point
state.ts — UI state management
search.ts — search logic
render.ts — results rendering
preview.ts — preview panel
events.ts — keyboard/mouse event handlers
contextMenu.ts — right-click context menu
highlight.ts — HTML escaping and query match highlighting
shiki.ts — Shiki syntax highlighter integration
test/ — unit tests (vitest)
scripts/ — release helpers (changelog section extraction)
.github/workflows/ — CI and release workflows
ui-tests/ — UI tests (Playwright): webview + real controller + real ripgrep, VS Code API mocked
🤝 Contributing
PRs and issues welcome at github.com/garroter/spyglass. See CONTRIBUTING.md for how to set up, test and submit a change.
📄 License
MIT



