Skip to main content

Window & UI API

Display notifications, dialogs, popups, webviews, bottom sheets, and other UI elements from your extension.

The editor.window namespace provides a comprehensive set of RPC methods for building interactive user interfaces. These methods let your extension show toast messages, prompt for input, display confirmation dialogs, open webview tabs, render markdown in popups, manage progress indicators, and more -- all without requiring any special permissions.

RPC Methods

window.showToast

Shows a brief notification at the bottom of the screen. The toast appears momentarily and dismisses itself automatically.

Parameters:

NameTypeRequiredDescription
messagestringYesThe text to display in the toast.

Returns: void

await editor.window.showToast("File saved successfully!");

Toasts are fire-and-forget -- there is no way to dismiss them programmatically or to detect when the user has seen them. Use toasts for brief, non-critical status updates.


window.showInputBox

Shows a modal dialog with a text input field. The call blocks until the user submits a value or cancels the dialog.

Parameters:

NameTypeRequiredDescription
promptstringYesThe title or prompt text shown above the input field.
placeholderstringNoPlaceholder text displayed inside the empty input field.
valuestringNoPre-filled value in the input field.
labelstringNoA label displayed alongside the input field.
descriptionstringNoDescriptive text providing additional context below the prompt.
infoCommandstringNoA command ID to execute when the user taps an info button on the dialog.

Returns: string | null -- The text entered by the user, or null if the dialog was cancelled.

const filename = await editor.window.showInputBox({
prompt: "Enter file name:",
placeholder: "e.g., index.html",
value: "untitled.txt",
label: "File Name",
description: "The name for the new file",
});

if (filename !== null) {
// User entered a value
await editor.window.showToast(`Creating file: ${filename}`);
} else {
// User cancelled
await editor.window.showToast("Operation cancelled.");
}

This method has a 5-minute timeout to allow ample time for user input. If the timeout is exceeded, the RPC call returns an error.


window.showConfirm

Shows a confirmation dialog with two buttons. The call blocks until the user responds.

Parameters:

NameTypeRequiredDescription
titlestringNoTitle of the confirmation dialog. Defaults to "Confirm".
messagestringNoThe question or message body to display.
confirmLabelstringNoCustom label for the confirm button (e.g., "Delete", "Yes, proceed").
cancelLabelstringNoCustom label for the cancel button (e.g., "No, cancel").

Returns: boolean -- true if the user confirmed, false if cancelled.

const confirmed = await editor.window.showConfirm({
title: "Delete File",
message: "Are you sure you want to delete this file? This cannot be undone.",
confirmLabel: "Delete",
cancelLabel: "Keep",
});

if (confirmed) {
await editor.workspace.fs.delete(fileUri);
await editor.window.showToast("File deleted.");
}

This method has a 5-minute timeout.


window.showPicker

Shows a searchable list dialog, similar to VS Code's Quick Pick. The user can type to filter items and select one.

Parameters:

NameTypeRequiredDescription
titlestringNoTitle displayed at the top of the picker.
placeholderstringNoPlaceholder text for the search field.
itemsArray<PickerItem>YesArray of items to display in the list.

Each item in the items array has the following shape:

NameTypeRequiredDescription
labelstringYesPrimary text for the item.
descriptionstringNoSecondary text displayed next to the label.
detailstringNoAdditional detail text displayed below the label.

Returns: { label, description?, detail? } | null -- The selected item object, or null if the picker was cancelled.

const selected = await editor.window.showPicker({
title: "Select Framework",
placeholder: "Search frameworks...",
items: [
{ label: "React", description: "A JavaScript library for building UIs" },
{ label: "Vue", description: "The Progressive JavaScript Framework" },
{ label: "Svelte", description: "Cybernetically enhanced web apps" },
{
label: "Angular",
description: "The modern web developer platform",
detail: "Requires TypeScript",
},
],
});

if (selected) {
await editor.window.showToast(`You selected: ${selected.label}`);
} else {
await editor.window.showToast("Selection cancelled.");
}

This method has a 5-minute timeout.


window.showPopup

Shows a modal popup dialog with rich content. Supports three content types: webview (HTML), markdown, and terminal.

Parameters:

NameTypeRequiredDescription
titlestringYesTitle shown in the popup's title bar.
contentTypestringNoOne of "webview", "markdown", or "terminal". Defaults to "webview".
urlstringNoURL to load in a webview popup. Must be an HTTP/HTTPS URL.
htmlstringNoInline HTML content for a webview popup.
markdownstringNoMarkdown content for a markdown popup.
widthFractionnumberNoWidth as a fraction of screen width (0.0--1.0). Defaults to 0.85.
heightFractionnumberNoHeight as a fraction of screen height (0.0--1.0). Defaults to 0.7.
canOpenAsTabbooleanNoWhether to show an "open in tab" button on the popup. Defaults to true.

Returns: string -- A popup ID that can be used with window.closePopup to dismiss the popup programmatically.

For contentType: "webview", you must provide either url or html. For contentType: "markdown", provide the markdown parameter. For contentType: "terminal", the popup renders an embedded terminal session.

Webview Popup

const popupId = await editor.window.showPopup({
title: "Extension Settings",
contentType: "webview",
html: `
<html>
<body style="padding: 24px; font-family: system-ui; background: #1a1a2e; color: #e0e0e0;">
<h1>Settings</h1>
<label>
<input type="checkbox" checked /> Enable auto-save
</label>
<br/>
<button onclick="window.post('my-ext.saveSettings', { autoSave: true })">
Save
</button>
</body>
</html>
`,
widthFraction: 0.5,
heightFraction: 0.6,
canOpenAsTab: false,
});

// Close the popup after the user saves settings
editor.listen("my-ext.saveSettings", async (data) => {
await editor.window.closePopup(popupId);
await editor.window.showToast("Settings saved!");
});

Markdown Popup

await editor.window.showPopup({
title: "Help",
contentType: "markdown",
markdown: `
# Keyboard Shortcuts

| Action | Shortcut |
|--------|----------|
| Save | Ctrl+S |
| Undo | Ctrl+Z |
| Find | Ctrl+F |

## Tips

- **Bold text** and *italic text* are supported
- Code blocks with syntax highlighting:

\`\`\`javascript
console.log("Hello World");
\`\`\`

> Blockquotes work too!
`,
});

Terminal Popup

await editor.window.showPopup({
title: "Build Output",
contentType: "terminal",
widthFraction: 0.9,
heightFraction: 0.8,
});

The terminal popup embeds a terminal view inside the popup dialog. This requires the terminal permission in your manifest.


window.closePopup

Closes an open popup by its ID. Silently succeeds if the popup is already closed or the ID is not found. If the popup was converted to a tab (via the "open in tab" button), the corresponding tab is also closed.

Parameters:

NameTypeRequiredDescription
popupIdstringYesThe popup ID returned by window.showPopup.

Returns: void

const popupId = await editor.window.showPopup({
title: "Processing...",
contentType: "markdown",
markdown: "# Please wait\n\nProcessing your request...",
});

// Do some work...
await someAsyncOperation();

// Dismiss the popup when done
await editor.window.closePopup(popupId);

window.showBottomSheet

Shows a draggable bottom sheet that slides up from the bottom of the screen. The sheet contains a WebView with your HTML content or a loaded URL.

Parameters:

NameTypeRequiredDescription
titlestringYesTitle displayed in the bottom sheet header.
urlstringNoURL to load in the bottom sheet's WebView. Must be an HTTP/HTTPS URL.
htmlstringNoInline HTML content for the bottom sheet's WebView.
showTitlebooleanNoWhether to show the title bar. Defaults to true.
showCloseButtonbooleanNoWhether to show a close button. Defaults to true.
showOpenInTabbooleanNoWhether to show an "open in tab" button. Defaults to true.

Returns: { bottomSheetId: string } -- An ID that can be used to close the bottom sheet programmatically.

You must provide either url or html. If both are empty, the call throws an error.

const { bottomSheetId } = await editor.window.showBottomSheet({
title: "Quick Actions",
html: `
<div style="padding: 16px; font-family: system-ui; color: #ccc; background: #1a1a2e;">
<h2 style="margin-top: 0;">Quick Actions</h2>
<button onclick="window.post('my-ext.action', { type: 'format' })"
style="padding: 8px 16px; margin: 4px; background: #7C3AED; color: white;
border: none; border-radius: 4px; cursor: pointer;">
Format Code
</button>
<button onclick="window.post('my-ext.action', { type: 'lint' })"
style="padding: 8px 16px; margin: 4px; background: #61AFEF; color: white;
border: none; border-radius: 4px; cursor: pointer;">
Run Linter
</button>
</div>
`,
showTitle: true,
showCloseButton: true,
showOpenInTab: false,
});

The user can drag the bottom sheet up and down. The bottom sheet stays open until the user dismisses it or the extension closes it programmatically using the returned bottomSheetId.


window.closeBottomSheet

Closes a bottom sheet that was opened by this extension using its ID.

Parameters:

NameTypeRequiredDescription
bottomSheetIdstringYesThe bottomSheetId returned by window.showBottomSheet.

Returns: void

const { bottomSheetId } = await editor.window.showBottomSheet({
title: "My Panel",
html: "<div>Hello</div>",
});

// Later, close it programmatically
await editor.window.closeBottomSheet(bottomSheetId);

window.showDirectoryPicker

Opens a directory picker dialog that lets the user select a folder from their file system. The picker supports both local filesystem paths and SAF (Storage Access Framework) URIs on Android.

Parameters:

NameTypeRequiredDescription
titlestringNoTitle displayed at the top of the picker.

Returns: string | null -- The URI of the selected directory, or null if the user cancelled.

const dirUri = await editor.window.showDirectoryPicker({
title: "Select Output Directory",
});

if (dirUri) {
await editor.window.showToast(`Selected: ${dirUri}`);
// Use the URI with workspace.fs methods
const files = await editor.workspace.fs.list(dirUri);
} else {
await editor.window.showToast("No directory selected.");
}

This method has a 5-minute timeout.


window.showFilePicker

Opens a file picker dialog that lets the user select a file from their file system. The picker supports both local filesystem paths and SAF (Storage Access Framework) URIs on Android.

Parameters:

NameTypeRequiredDescription
titlestringNoTitle displayed at the top of the picker.

Returns: string | null -- The URI of the selected file, or null if the user cancelled.

const fileUri = await editor.window.showFilePicker({
title: "Select a file",
});

if (fileUri) {
const content = await editor.workspace.fs.read(fileUri);
await editor.window.showToast(`File has ${content.length} characters`);
} else {
await editor.window.showToast("No file selected.");
}

This method has a 5-minute timeout.


window.showProgress

Shows a loading overlay with a message. The overlay covers the screen until you explicitly hide it with window.hideProgress.

Parameters:

NameTypeRequiredDescription
messagestringYesThe progress message to display.

Returns: { progressId: string } -- An object containing the progress ID needed to dismiss the overlay.

const { progressId } = await editor.window.showProgress("Installing dependencies...");

Always pair showProgress with hideProgress in a try/finally block to ensure the overlay is dismissed even if an error occurs.


window.hideProgress

Hides a progress overlay that was previously shown by window.showProgress.

Parameters:

NameTypeRequiredDescription
progressIdstringYesThe progressId returned by window.showProgress.

Returns: void

const { progressId } = await editor.window.showProgress("Building project...");

try {
// Perform the long-running operation
await buildProject();
await editor.window.showToast("Build complete!");
} catch (err) {
await editor.window.showToast(`Build failed: ${err.message}`);
} finally {
await editor.window.hideProgress(progressId);
}

window.setStatusBarText

Dynamically updates the label text of a status bar item. The item must be declared in your extension's manifest.json under contributes.statusBarItems.

Parameters:

NameTypeRequiredDescription
idstringYesThe status bar item ID, as declared in your manifest.
textstringYesThe new label text to display.

Returns: void

First, declare the status bar item in your manifest.json:

{
"contributes": {
"statusBarItems": [
{
"id": "my-ext.status",
"label": "Ready",
"commandId": "my-ext.toggleStatus",
"alignment": "left",
"priority": 100
}
]
}
}

Then update the text dynamically from your extension code:

await editor.window.setStatusBarText("my-ext.status", "Building...");

// After the build completes:
await editor.window.setStatusBarText("my-ext.status", "Ready");

If the id does not match any registered status bar item, the call is silently ignored.


window.showCommandPalette

Opens the command palette, filtered to show only commands registered by the calling extension. This is useful for extensions that want to provide a discoverable command menu.

Parameters: None.

Returns: void

await editor.window.showCommandPalette();

window.showThemePicker

Opens the theme picker dialog, allowing the user to browse and select from all available themes (including themes contributed by extensions).

Parameters: None.

Returns: void

await editor.window.showThemePicker();

window.openWebviewTab

Opens a new tab in the editor containing a WebView. The tab can load a URL or render inline HTML content.

Parameters:

NameTypeRequiredDescription
titlestringNoTitle for the tab. Defaults to "Webview".
urlstringNoURL to load in the WebView. Must be an HTTP/HTTPS URL.
htmlstringNoInline HTML content to render.

Returns: { tabKey: string } -- An object containing the tab key for the opened webview tab.

You must provide either url or html. If both are empty, the call throws an error.

// Load a URL
await editor.window.openWebviewTab({
title: "Documentation",
url: "https://docs.example.com",
});

// Render inline HTML
await editor.window.openWebviewTab({
title: "My Dashboard",
html: `
<html>
<body style="margin: 0; padding: 24px; font-family: system-ui;
background: #1a1a2e; color: #e0e0e0;">
<h1>Dashboard</h1>
<p>Extension uptime: <span id="time">0</span>s</p>
<script>
let seconds = 0;
setInterval(() => {
document.getElementById("time").textContent = ++seconds;
}, 1000);
</script>
</body>
</html>
`,
});

If the editor is not currently on the editor screen when openWebviewTab is called, the content is shown in a popup instead of a new tab. The popup includes a "open in tab" button so the user can move it to a tab later.

Security Restrictions

  • Only HTTP/HTTPS URLs are accepted. Extensions cannot load local files directly via URL.
  • URLs targeting restricted ports are also blocked.

The window.showPopup method supports three content types, each rendering a different kind of content inside the popup dialog:

Content TypeDescriptionRequired Parameter
webviewRenders a WebView with HTML content or a loaded URL. Supports interactive elements, JavaScript, and CSS.url or html
markdownRenders Markdown content with full formatting support including headings, bold/italic, code blocks, tables, links, blockquotes, and lists.markdown
terminalEmbeds an interactive terminal session inside the popup. Requires the terminal permission in your manifest.None

The widthFraction and heightFraction parameters control the popup size as a fraction of the screen dimensions:

ParameterDefaultRangeDescription
widthFraction0.850.0--1.0Width as a percentage of screen width. 0.5 = 50% of screen width.
heightFraction0.70.0--1.0Height as a percentage of screen height. 0.7 = 70% of screen height.

Open as Tab

When canOpenAsTab is true (the default), the popup includes an "open in tab" button in its title bar. Tapping this button converts the popup into a full editor tab, which is useful for content the user wants to keep visible while working.


Webview Communication

Webviews rendered inside popups, bottom sheets, and webview tabs can communicate with your extension using the messaging API. This is the same channel-based pub/sub system used for inter-extension messaging.

Inside the Webview (HTML)

<script>
// Send data to the extension
window.post("my-ext.formSubmit", { name: "John", email: "[email protected]" });

// Receive data from the extension
window.listen("my-ext.formResponse", (message) => {
document.getElementById("status").textContent = message.status;
});
</script>

Inside the Extension

// Receive data from the webview
const unsub = editor.listen("my-ext.formSubmit", async (data) => {
console.log("Form data:", data);

// Process the data and send a response back
await editor.post("my-ext.formResponse", { status: "Saved successfully" });
});

// Later, to stop listening:
unsub();

The messaging channel is global -- any extension or webview that listens on the same channel ID will receive the message. Use your extension ID as a namespace prefix (e.g., "my-ext.channelName") to avoid collisions with other extensions.


Timeouts

Different window methods have different timeout durations:

TimeoutMethods
30 secondsshowToast, showPopup, closePopup, closeBottomSheet, showProgress, hideProgress, setStatusBarText, showCommandPalette, showThemePicker, openWebviewTab
5 minutesshowInputBox, showConfirm, showPicker, showDirectoryPicker, showFilePicker
No timeoutshowBottomSheet (stays open until dismissed by user or closed programmatically)

Interactive methods that wait for user input have the longer timeout to avoid timing out while the user is reading or typing. If a timeout is exceeded, the RPC call returns an "RPC timeout" error.


Examples

Interactive Project Scaffolder

Combine multiple window methods to create a guided workflow:

editor.commands.registerCommand("my-ext.newProject", async () => {
// Step 1: Pick a template
const template = await editor.window.showPicker({
title: "Select Project Template",
placeholder: "Search templates...",
items: [
{ label: "React App", description: "Create React App with TypeScript" },
{ label: "Node API", description: "Express.js REST API starter" },
{ label: "Static Site", description: "HTML/CSS/JS boilerplate" },
],
});

if (!template) return;

// Step 2: Get the project name
const name = await editor.window.showInputBox({
prompt: "Project Name",
placeholder: "my-awesome-project",
});

if (!name) return;

// Step 3: Choose output directory
const dirUri = await editor.window.showDirectoryPicker({
title: "Where should we create the project?",
});

if (!dirUri) return;

// Step 4: Confirm
const ok = await editor.window.showConfirm({
title: "Create Project?",
message: `Create "${name}" using the ${template.label} template?`,
confirmLabel: "Create",
cancelLabel: "Cancel",
});

if (!ok) return;

// Step 5: Create with progress
const { progressId } = await editor.window.showProgress(
`Creating ${name}...`
);

try {
await editor.workspace.project.create({
name: name,
parentUri: dirUri,
files: generateTemplateFiles(template.label),
});

await editor.window.showToast(`Project "${name}" created successfully!`);
} finally {
await editor.window.hideProgress(progressId);
}
});

Build Status in the Status Bar

Use status bar text updates to show ongoing build state:

async function runBuild() {
await editor.window.setStatusBarText("my-ext.status", "Building...");

try {
const { tabKey } = await editor.terminal.open({
name: "Build",
command: "npm run build",
});

// Poll for completion
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 2000));
const { output } = await editor.terminal.readOutput(tabKey);

if (output.includes("Build complete")) {
await editor.window.setStatusBarText("my-ext.status", "Build OK");
await editor.terminal.close(tabKey);
return;
}
if (output.includes("ERROR")) {
await editor.window.setStatusBarText("my-ext.status", "Build Failed");
return;
}
}

await editor.window.setStatusBarText("my-ext.status", "Build Timeout");
} catch (err) {
await editor.window.setStatusBarText("my-ext.status", "Error");
}
}

Markdown Documentation Viewer

Load and display documentation in a popup:

editor.commands.registerCommand("my-ext.showDocs", async () => {
const activeFile = await editor.workspace.getActiveFile();
const ext = activeFile?.name.split(".").pop() || "txt";

const docs = {
js: "# JavaScript\n\nUse `console.log()` for debugging.\n\n## Common Patterns\n- Arrow functions\n- Destructuring\n- Template literals",
py: "# Python\n\nUse `print()` for debugging.\n\n## Common Patterns\n- List comprehensions\n- f-strings\n- Decorators",
html: "# HTML\n\nStructure your document with semantic elements.\n\n## Common Elements\n- `<header>`, `<main>`, `<footer>`\n- `<article>`, `<section>`\n- `<nav>`, `<aside>`",
};

await editor.window.showPopup({
title: `${ext.toUpperCase()} Quick Reference`,
contentType: "markdown",
markdown: docs[ext] || `# ${ext.toUpperCase()}\n\nNo documentation available for this file type.`,
widthFraction: 0.7,
heightFraction: 0.6,
});
});

Interactive Settings Panel with Bottom Sheet

editor.commands.registerCommand("my-ext.openSettings", async () => {
const { bottomSheetId } = await editor.window.showBottomSheet({
title: "Extension Settings",
html: `
<div style="padding: 16px; font-family: system-ui; color: #e0e0e0; background: #1e1e2e;">
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 4px; color: #888;">Theme</label>
<select id="theme" style="width: 100%; padding: 8px; background: #2a2a3e;
color: #e0e0e0; border: 1px solid #444; border-radius: 4px;">
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
</div>
<div style="margin-bottom: 16px;">
<label style="display: flex; align-items: center; gap: 8px; color: #e0e0e0;">
<input type="checkbox" id="autoSave" checked />
Enable auto-save
</label>
</div>
<button onclick="saveSettings()"
style="padding: 10px 20px; background: #7C3AED; color: white;
border: none; border-radius: 6px; cursor: pointer; width: 100%;">
Save Settings
</button>
</div>
<script>
function saveSettings() {
window.post('my-ext.settings.save', {
theme: document.getElementById('theme').value,
autoSave: document.getElementById('autoSave').checked,
});
}
</script>
`,
showTitle: true,
showCloseButton: true,
showOpenInTab: false,
});
});

// Handle settings save from the bottom sheet
editor.listen("my-ext.settings.save", async (settings) => {
await editor.storage.kv.setItem("settings", JSON.stringify(settings));
await editor.window.showToast("Settings saved!");
});

Diff Editor

To open a diff editor comparing two texts, see the Diff Editor API.