TL;DR
Tracking active files in a VS Code extension requires choosing between three APIs: window.activeTextEditor (current focused file), window.tabGroups (all open tabs), and workspace.textDocuments (all loaded documents). Each API serves a different use case and fires different events. This tutorial covers all three with production TypeScript implementations.
The Three File Tracking APIs
VS Code exposes file state through three distinct APIs. Understanding which to use — and when — is the difference between a reliable context system and a fragile one:
window.activeTextEditor
Returns the currently focused editor. Updates when the developer switches tabs. Use this for: 'what file is the developer looking at RIGHT NOW?' Event: window.onDidChangeActiveTextEditor. Limitation: only tracks one file at a time.
window.tabGroups.all
Returns all open tabs across all tab groups. Includes: file URI, active state, pinned state. Use this for: 'what files does the developer consider part of their working set?' Event: window.tabGroups.onDidChangeTabs. Available since VS Code 1.67.
workspace.textDocuments
Returns all documents loaded in memory. Includes files open in editors, files loaded by language servers, and files opened by other extensions. Use this for: 'what files is VS Code aware of?' Event: workspace.onDidOpenTextDocument. Warning: includes many files the developer hasn't explicitly opened.
Production Implementation
Here's the production TypeScript implementation for tracking all three file states:
// File state tracker — production implementation
interface FileState {
activeFile: string | undefined;
openTabs: string[];
loadedDocuments: string[];
}
function getFileState(): FileState {
const activeFile = vscode.window.activeTextEditor?.document.uri.fsPath;
const openTabs = vscode.window.tabGroups.all
.flatMap(group => group.tabs)
.filter(tab => tab.input instanceof vscode.TabInputText)
.map(tab => (tab.input as vscode.TabInputText).uri.fsPath);
const loadedDocuments = vscode.workspace.textDocuments
.filter(doc => doc.uri.scheme === 'file')
.map(doc => doc.uri.fsPath);
return { activeFile, openTabs, loadedDocuments };
}
The Event Subscription Pattern
For a context engine, you need real-time updates. Here's the event-driven pattern:
Subscribe to Active Editor Changes
vscode.window.onDidChangeActiveTextEditor fires when the developer switches tabs. This is your highest-frequency, highest-priority event. Debounce at 100ms to avoid thrashing during rapid tab switches.
Subscribe to Tab Changes
vscode.window.tabGroups.onDidChangeTabs fires when tabs are opened, closed, or moved. This updates your working set map. No debounce needed — tab events are infrequent.
Subscribe to Document Changes
vscode.workspace.onDidChangeTextDocument fires on every edit. Use this for detecting modified files, not for tracking file state. Debounce at 500ms — edits fire hundreds of times per second during typing.
Compute State on Every Event
On any event, recompute the full FileState object and emit it to your context engine. The computation takes <1ms. Don't try to incrementally update state — full recomputation is faster and more reliable.
Why This Matters for AI Context
The files a developer has open in their editor are the strongest signal of what's relevant to their current task. They opened those files intentionally. Tracking them accurately and continuously is the foundation of deterministic AI context.
🔧 This is how Context Snipe tracks your files.
Context Snipe's VS Code extension uses all three file tracking APIs to build a real-time picture of your IDE state. Open tabs, focused file, loaded documents — all fed into the AI context automatically. Start free — no credit card →