Skip to content
docs
GitHub

Commands and interface slots

Declare commands, keyboard shortcuts, slot controls and local plugin styles.

Declare each command in contributes.commands, then register its handler during activate(). The starter plugin does both for example.workspace-info.open.

Declared commands appear in Commands and Settings > Keybinds before lazy activation. The host activates the owner when a command is executed. A declaration without a registered handler fails with The plugin did not register this command.

Context controls when a command is available:

Field Behavior
workspace: true Requires an active workspace. Omitted or false adds no workspace requirement.
viewTypes: [...] Requires the active panel type to match one of the values. Use built-in types such as terminal or a full plugin view ID. An empty array matches nothing.
textInput: true Allows keyboard execution while typing in an input. The default blocks that keyboard context.

textInput: false is not a ban on calling the command from code. context.executeCommand(id) uses the SDK’s programmatic execution path, which does not infer the currently focused input.

This fragment belongs inside contributes:

{
"keybindings": [
{
"command": "example.workspace-info.open",
"shortcut": "Ctrl+Alt+KeyI"
}
]
}

Use keyboard codes such as KeyI, Digit1, Enter or F6, not display labels such as I. Modifier order is Ctrl, Alt, Meta, Shift; omit unused modifiers. Except for function keys F1 to F24, a binding needs a modifier other than Shift. null leaves the command without a default binding.

On macOS, default Ctrl bindings become Meta and appear as Cmd in the interface. Users can change or disable them in Keybinds. A conflicting default remains unassigned; it cannot take over another command’s shortcut. User assignments are kept by command ID when a plugin is absent.

Slot Location
statusbar Main-window status bar.
sidebar-actions Actions area of plugin sidebars.
view-actions Heading of plugin panels.

Declare a fill with id, slot and label, then call registerFill(id, Component). Fills are ordered by contribution ID. They are slot-wide, not automatically limited to views from the same plugin. Use useHostContext() when a control should depend on the active view or workspace.

Put an accessible label on the actual button or input. The manifest label does not replace a button’s accessible name.

Fill components appear only after activation. A plugin that should add a status control as soon as the application is ready needs activation: "startup"; a lazy plugin with only a fill has no view or command trigger to activate it.

Start with the panel template using ID example.workspace-info. Set activation to startup and add this inside contributes:

{
"fills": [
{
"id": "example.workspace-info.status",
"slot": "statusbar",
"label": "Open workspace information"
}
]
}

Keep the existing views and commands declarations. Replace src/index.tsx with:

import { useState } from 'react';
import {
useHostContext,
type PluginContext,
type ViewProps,
} from '@lomi-dev/plugin-sdk';
function WorkspaceInfo({ panel }: ViewProps) {
const host = useHostContext();
return (
<section className="lomi-workspace-info">
<h2>{host.workspaceName ?? 'No workspace selected'}</h2>
<p>{host.projectPath ?? 'No project selected'}</p>
<small>Panel: {panel.id}</small>
</section>
);
}
export function activate(context: PluginContext) {
context.registerView('example.workspace-info.view', WorkspaceInfo);
context.registerCommand('example.workspace-info.open', async () => {
await context.openView('example.workspace-info.view');
});
function OpenButton() {
const host = useHostContext();
const [error, setError] = useState('');
async function open() {
setError('');
try { await context.executeCommand('example.workspace-info.open'); }
catch (error) {
setError(error instanceof Error ? error.message : String(error));
}
}
return (
<span className="lomi-workspace-info-action">
<button type="button" disabled={!host.workspaceName} onClick={open}>
Workspace info
</button>
{error && <span role="alert">{error}</span>}
</span>
);
}
context.registerFill('example.workspace-info.status', OpenButton);
}

The button is disabled without a workspace and displays command failures. The host removes registered views, commands and fills on deactivation. Update the generated example for this changed view: this example has no Show project path checkbox.

To limit a control to your active view, check host.viewType === 'example.workspace-info.view'. Slots do not pass ViewProps or the ID of the panel where a control is rendered; the snapshot describes the application’s active panel.

executeCommand() can invoke another plugin’s declared command, but it does not install or trust that plugin. Missing packages, disabled code, unavailable context and activation failures reject the promise. Handle this in your interface. Manifest v1 cannot enforce inter-plugin dependencies, and commands accept no arguments or return values through the SDK.

Do not intercept global keydown to bypass shortcut conflicts. Use declarations and Settings > Keybinds. Valid examples are Ctrl+Alt+KeyI, Meta+Shift+Digit1 and F6. Cmd+I, Ctrl+I, Shift+KeyI, repeated modifiers and multi-step key sequences are invalid.

See Styles and assets for styling the button and including graphics.

Source: command execution, shortcuts, slot rendering.