Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>Code TelescopeNew to Visual Studio Code? Get it now.
Code Telescope

Code Telescope

guichina

|
2 installs
| (0) | Free
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Code Telescope

A Telescope-inspired fuzzy finder for VS Code, bringing the power and flexibility of Neovim's Telescope to Visual Studio Code.

Motivation

Telescope.nvim revolutionized navigation in Neovim with its extensible fuzzy finder architecture. This project brings that same philosophy to VS Code: a single, powerful interface for finding files, searching text, browsing git commits...;

Architecture Overview

The architecture is built on three core principles:

  1. Annotation-based adapters for extensibility
  2. Clear separation between backend (extension) and UI (webview)
  3. Type-safe communication through shared interfaces
+-------------------------------------------------------------------+
|                    Extension Host (Backend)                       |
|                                                                   |
|  +--------------------+          +---------------------+          |
|  |  Finder Providers  |          |  Preview Renderers  |          |
|  |  @FuzzyFinder()    |          |  @PreviewRenderer() |          |
|  +---------+----------+          +----------+----------+          |
|            |                                |                     |
|            +-------------+------------------+                     |
|                          |                                        |
|                  +-------v-------+                                |
|                  | Presentation  |                                |
|                  |     Layer     |                                |
|                  | +------------+|                                |
|                  | |  Message   ||  - WebviewController           |
|                  | |  Handlers  ||  - Registry dispatching        |
|                  | |  Registry  ||  - HTML resolution             |
|                  | +------------+|                                |
|                  +-------+-------+                                |
|                          |                                        |
+--------------------------+----------------------------------------+
                           |
                    Message Protocol
                  (Type-safe interface)
                           |
+--------------------------+----------------------------------------+
|                          |                                        |
|                  +-------v-------+           Webview (UI)         |
|                  | Presentation  |                                |
|                  |     Layer     |                                |
|                  | +------------+|                                |
|                  | |  Webview   ||  - WebviewController           |
|                  | | Controller ||  - Message routing             |
|                  | |  Keyboard  ||  - Event handling              |
|                  | |  Handlers  ||  - State management            |
|                  | +------------+|                                |
|                  +-------+-------+                                |
|                          |                                        |
|                    +-----v----+                                   |
|                    |  Shared  |                                   |
|                    |   Types  |                                   |
|                    +-----+----+                                   |
|                          |                                        |
|       +------------------+-----------------+                      |
|       |                                    |                      |
|  +----v-------------+          +-----------v----------+           |
|  |  Data Adapters    |          |  Renderer Adapters    |         |
|  |  (parse & filter) |          |  (display previews)   |         |
|  +-------------------+          +-----------------------+         |
|                                                                   |
+-------------------------------------------------------------------+

Core Concepts

Finders (Backend)

Finders are data providers that supply items to the fuzzy finder. Each finder is registered via the @FuzzyFinderAdapter decorator.

@FuzzyFinderAdapter({
  fuzzy: "workspace.files",
  previewRenderer: "preview.codeHighlighted",
})
export class WorkspaceFileProvider implements IFuzzyFinderProvider {
  fuzzyAdapterType!: FuzzyProviderType;
  previewAdapterType!: PreviewRendererType;

  async querySelectableOptions(): Promise<any> {
    // Return list of files
  }

  async getPreviewData(identifier: string): Promise<PreviewData> {
    // Return file content for preview
  }

  async onSelect(identifier: string): Promise<void> {
    // Open selected file
  }
}

Key responsibilities:

  • Query and return selectable items
  • Provide preview data for selected items
  • Execute action on item selection
  • Support dynamic search (optional)

Previewers (Backend)

Preview renderers transform raw data into visual representations. Registered via @PreviewRendererAdapter.

@PreviewRendererAdapter({
  adapter: "preview.codeHighlighted",
})
export class CodeHighlightedPreviewRenderer implements IPreviewRendererAdapter {
  async render(previewElement: HTMLElement, data: PreviewData, theme: string): Promise<void> {
    // Render syntax-highlighted code
  }
}

Data Adapters (UI)

UI-side adapters handle data transformation and filtering for specific finder types.

export class FileDataAdapter implements IFuzzyFinderDataAdapter {
  parseOptions(data: any): FileOption[] {
    // Convert backend data to UI options
  }

  getDisplayText(option: FileOption): string {
    // Format option for display
  }

  filterOption(option: FileOption, query: string): boolean {
    // Custom filtering logic
  }
}

Message Protocol

Type-safe communication between extension and webview through shared interfaces:

// Backend → Webview
interface ToWebviewKindMessage {
  type: "options" | "preview" | "theme" | ...;
  payload: any;
}

// Webview → Backend
interface FromWebviewKindMessage {
  type: "requestOptions" | "requestPreview" | "select" | ...;
  payload: any;
}

Features

Keyboard Navigation

  • ↑/↓ or Ctrl+K/J: Navigate between items
  • Ctrl+U/D: Navigate preview up/down
  • Enter: Select item
  • Esc: Close finder
  • Type to filter results in real-time

Dynamic Search

Finders can opt into dynamic search mode, where queries are sent to the backend for server-side filtering (useful for large datasets like workspace text search).

export class WorkspaceTextSearchProvider implements IFuzzyFinderProvider {
  supportsDynamicSearch = true;

  async searchOnDynamicMode(query: string): Promise<any> {
    // Execute search and return results
  }
}

Extensibility

Adding a new finder requires:

  1. Backend: Create a provider implementing IFuzzyFinderProvider
  2. UI: Create a data adapter implementing IFuzzyFinderDataAdapter
  3. Annotations: Decorate with @FuzzyFinderAdapter and register types

The system automatically wires everything together through the type system.

Built-in Finders

  • Workspace Files: Find files in the current workspace
  • Workspace Text Search: Search text across files using ripgrep
  • Git Commits: Browse git history with diff previews
  • Git Branches: Quickly switch and inspect git branches
  • Keybindings: Browse and execute registered VS Code keybindings
  • Custom: Quick picker for user-defined custom finders

(More coming soon...)

Extending Code Telescope

Code Telescope is designed to be extensible. You can create your own custom finders without modifying the extension code.

Creating Custom Finders

Custom finders are defined as CommonJS modules (.cjs files) placed in the .vscode/code-telescope/ directory of your workspace.

File Location

Create your custom finder in:

.vscode/code-telescope/my-custom.finder.cjs

Naming convention:

  • Must end with .finder.cjs or .provider.cjs
  • Use descriptive names (e.g., github-issues.finder.cjs, database-tables.finder.cjs)

Basic Structure

A custom finder must export a CustomFinderDefinition object that defines both backend logic and UI adapters:

// .vscode/code-telescope/example.finder.cjs

/** @type {import('code-telescope/shared/custom-provider').CustomFinderDefinition} */
module.exports = {
  // Unique identifier (must start with "custom.")
  fuzzyAdapterType: "custom.example",
  
  // Backend logic (runs in extension host)
  backend: {
    async querySelectableOptions() {
      // Return data to be displayed
      return {
        items: ["Item 1", "Item 2", "Item 3"]
      };
    },
    
    async onSelect(item) {
      // Handle selection
      return {
        data: item,
        action: "showMessage" // Built-in action
      };
    },
    
    async getPreviewData(identifier) {
      // Return preview data for syntax-highlighted code view
      return {
        content: {
          path: "Preview Title",
          content: `Content for: ${identifier}`,
          kind: "text"
        },
        language: "text"
      };
    }
  },
  
  // UI adapters (runs in webview)
  ui: {
    dataAdapter: {
      parseOptions(data) {
        // Transform backend data into options
        return data.items.map((item, index) => ({
          id: index,
          text: item
        }));
      },
      
      getDisplayText(option) {
        // Format option for display
        return option.text;
      },
      
      getSelectionValue(option) {
        // Return identifier for selection
        return option.text;
      },
      
      filterOption(option, query) {
        // Custom filtering logic (optional)
        return option.text.toLowerCase().includes(query.toLowerCase());
      }
    }
  }
};

API Reference

Backend Methods

querySelectableOptions()

Called when the finder is opened. Should return data that will be transformed by the UI adapter.

Signature:

async querySelectableOptions(): Promise<any>

Returns: Any data structure that your UI adapter's parseOptions can handle.

Example:

async querySelectableOptions() {
  return {
    items: [
      { id: 1, name: "Item 1" },
      { id: 2, name: "Item 2" }
    ]
  };
}

onSelect(item)

Called when user selects an item. Should return action data.

Signature:

async onSelect(item: any): Promise<{
  data: any;
  action: string;
}>

Parameters:

  • item - The selected item (as returned by UI adapter's getSelectionValue)

Returns: An object with:

  • path — File path to be handled
  • action — Action identifier

Or void, if the selection is handled internally by your own callback.

Built-in actions:

  • "openFile" - Opens file at data (must be a file path)
  • "none" - No automatic action (you handled it manually in onSelect)

Example:

async onSelect(itemId) {
  const item = await fetchItemDetails(itemId);
  
  return {
    path: item,
    action: "openFile"
  };
}

getPreviewData(identifier)

Returns data for the preview panel. This data is rendered as syntax-highlighted code.

Signature:

async getPreviewData(identifier: any): Promise<{
  content: string;
  language: string;
}>

Parameters:

  • identifier - The value returned by UI adapter's getSelectionValue

Returns: Object with:

  • content - Text to be syntax-highlighted
  • language - Language identifier for syntax highlighting (e.g., "javascript", "python", "json")

Example:

async getPreviewData(fileId) {
  const file = await fetchFile(fileId);
  const extension = path.extname(file.name).slice(1);
  
  return {
    content: file.content,
    language: extension || "text"
  };
}

Supported languages: All languages supported by VS Code's syntax highlighting (javascript, typescript, python, java, json, markdown, etc.)


UI Data Adapter Methods

parseOptions(data)

Transforms backend data into an array of options.

Signature:

parseOptions(data: any): any[]

Parameters:

  • data - Data returned by backend's querySelectableOptions

Returns: Array of options to be displayed in the list.

Example:

parseOptions(data) {
  return data.items.map(item => ({
    id: item.id,
    name: item.name,
    description: item.description
  }));
}

getDisplayText(option)

Returns the text displayed in the list for an option.

Signature:

getDisplayText(option: any): string

Parameters:

  • option - One option from the array returned by parseOptions

Returns: String to be displayed in the finder list.

Example:

getDisplayText(option) {
  // Format with padding for alignment
  return `${option.name.padEnd(30)} ${option.description}`;
}

getSelectionValue(option)

Returns identifier used for selection and preview.

Signature:

getSelectionValue(option: any): string

Parameters:

  • option - One option from the array returned by parseOptions

Returns: String identifier passed to onSelect and getPreviewData.

Example:

getSelectionValue(option) {
  // Return a unique identifier
  return option.id.toString();
}

filterOption(option, query) (optional)

Custom filtering logic. If not provided, uses default fuzzy matching on getDisplayText result.

Signature:

filterOption(option: any, query: string): boolean

Parameters:

  • option - One option from the array returned by parseOptions
  • query - Current search query (lowercase)

Returns: true if option matches the query, false otherwise.

Example:

filterOption(option, query) {
  const lowerQuery = query.toLowerCase();
  return (
    option.name.toLowerCase().includes(lowerQuery) ||
    option.description.toLowerCase().includes(lowerQuery)
  );
}

Flow:

  1. User opens custom finder
  2. Backend's querySelectableOptions() is called
  3. UI's parseOptions() transforms the data
  4. User types → UI's filterOption() filters results
  5. User navigates → Backend's getPreviewData() shows preview
  6. User selects → Backend's onSelect() executes action

Examples

Complete working examples are available in the examples/ directory:

  • custom-json.finder.cjs - Find json files

Debugging Custom Finders

  1. Check the Developer Console
    Open with Help > Toggle Developer Tools in VS Code

  2. Add logging in your finder:

    async querySelectableOptions() {
      console.log("[Custom Finder] Querying options...");
      const result = await fetchData();
      console.log("[Custom Finder] Found:", result.length, "items");
      return result;
    }
    
  3. Validate data structure
    Ensure your backend returns what your UI adapter expects


Limitations

  • Custom finders run in the extension host (Node.js environment)
  • Cannot use browser-only APIs
  • Must use CommonJS module format (.cjs)
  • Preview is always rendered as syntax-highlighted code
  • No access to extension's internal state

Why This Architecture?

  1. Loose coupling: Finders don't know about UI, UI doesn't know about implementation details
  2. Easy testing: Each component can be tested independently
  3. Type safety: Compile-time guarantees prevent integration bugs
  4. Extensibility: Add new finders without touching existing code
  5. Consistency: Single UX pattern for all finder types

Inspired by telescope.nvim 🔭

Contributing

Found a bug or have a feature request? Please open an issue.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft