Plugin Development Guide
Learn how to create powerful plugins for Lokus using our comprehensive plugin architecture with React integration, status bar components, and secure API access.
Plugin Architecture Overview
Lokus features a modular plugin system with:
- React Integration: Full React component support with global React access
- Status Bar API: Create custom status bar components
- Secure Runtime: Plugin sandboxing and security management
- Plugin Registry: Discovery and management system
- Inter-Plugin Communication: Standardized messaging patterns
Getting Started
Prerequisites
- Node.js 18+
- Basic React knowledge
- TypeScript familiarity (recommended)
- Lokus development environment
Plugin CLI
Use the official Lokus Plugin CLI for scaffolding:
npm install -g @lokus/plugin-cli
# Create a new plugin
lokus-plugin create my-awesome-plugin
cd my-awesome-plugin
# Development mode
npm run dev
# Build for production
npm run build
Plugin Structure
my-plugin/
├── src/
│ ├── index.ts # Main plugin entry
│ ├── components/ # React components
│ ├── hooks/ # Custom React hooks
│ └── utils/ # Utility functions
├── package.json # Plugin manifest
├── plugin.config.js # Plugin configuration
└── README.md # Plugin documentation
Basic Plugin Example
Simple Status Bar Plugin
// src/index.ts
import React from 'react';
import { LokusPlugin, StatusBarComponent } from '@lokus/plugin-api';
interface WordCountState {
words: number;
characters: number;
}
const WordCountComponent: StatusBarComponent = () => {
const [stats, setStats] = React.useState<WordCountState>({
words: 0,
characters: 0
});
// Subscribe to editor changes
React.useEffect(() => {
const unsubscribe = lokus.editor.onContentChange((content: string) => {
const words = content.trim().split(/\s+/).filter(Boolean).length;
const characters = content.length;
setStats({ words, characters });
});
return unsubscribe;
}, []);
return React.createElement('div', {
style: {
display: 'flex',
gap: '8px',
fontSize: '12px',
color: 'var(--lokus-text-secondary)'
}
}, [
React.createElement('span', { key: 'words' }, `${stats.words} words`),
React.createElement('span', { key: 'chars' }, `${stats.characters} chars`)
]);
};
const wordCountPlugin: LokusPlugin = {
id: 'word-count',
name: 'Word Count',
version: '1.0.0',
description: 'Display word and character count in status bar',
activate(context) {
// Register status bar component
context.statusBar.register('word-count', WordCountComponent, {
position: 'right',
priority: 100
});
},
deactivate(context) {
// Cleanup
context.statusBar.unregister('word-count');
}
};
export default wordCountPlugin;
Plugin Manifest (package.json)
{
"name": "@lokus/word-count-plugin",
"version": "1.0.0",
"description": "Word and character counter for Lokus",
"main": "dist/index.js",
"keywords": ["lokus", "plugin", "productivity"],
"lokus": {
"id": "word-count",
"displayName": "Word Count",
"categories": ["productivity", "editor"],
"activationEvents": ["onStartup"],
"contributes": {
"statusBar": ["word-count"]
}
},
"dependencies": {
"@lokus/plugin-api": "^1.0.0"
}
}
Status Bar Plugin Development
Creating Status Bar Components
Status bar plugins can display real-time information and interactive elements:
import React from 'react';
import { StatusBarComponent } from '@lokus/plugin-api';
const PomodoroTimer: StatusBarComponent = () => {
const [time, setTime] = React.useState(25 * 60); // 25 minutes
const [isRunning, setIsRunning] = React.useState(false);
const [isBreak, setIsBreak] = React.useState(false);
React.useEffect(() => {
let interval: NodeJS.Timeout;
if (isRunning && time > 0) {
interval = setInterval(() => {
setTime(time => time - 1);
}, 1000);
} else if (time === 0) {
// Timer finished
setIsRunning(false);
setIsBreak(!isBreak);
setTime(isBreak ? 25 * 60 : 5 * 60); // 25min work, 5min break
// Show notification
lokus.notifications.show({
title: isBreak ? 'Break time!' : 'Back to work!',
type: 'info'
});
}
return () => clearInterval(interval);
}, [isRunning, time, isBreak]);
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
const toggleTimer = () => setIsRunning(!isRunning);
const resetTimer = () => {
setIsRunning(false);
setTime(25 * 60);
setIsBreak(false);
};
return React.createElement('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: '4px',
cursor: 'pointer',
padding: '4px 8px',
borderRadius: '4px',
backgroundColor: isBreak ? 'var(--lokus-success)' : 'var(--lokus-primary)',
color: 'white',
fontSize: '11px',
fontFamily: 'monospace'
},
onClick: toggleTimer,
onContextMenu: (e: React.MouseEvent) => {
e.preventDefault();
resetTimer();
}
}, [
React.createElement('span', { key: 'icon' }, isBreak ? 'Break' : 'Work'),
React.createElement('span', { key: 'time' }, formatTime(time)),
React.createElement('span', {
key: 'status',
style: { fontSize: '8px', opacity: 0.8 }
}, isRunning ? '⏸' : '▶')
]);
};
Status Bar Registration Options
context.statusBar.register('component-id', Component, {
position: 'left' | 'center' | 'right', // Status bar position
priority: number, // Display order (higher = more left/right)
condition: () => boolean, // When to show component
tooltip: 'Component description' // Hover tooltip
});
Advanced Plugin Features
React Integration
Lokus provides full React integration for plugins:
// Global React is available
const { useState, useEffect, useMemo } = React;
// Custom hooks
const useEditorContent = () => {
const [content, setContent] = useState('');
useEffect(() => {
const unsubscribe = lokus.editor.onContentChange(setContent);
return unsubscribe;
}, []);
return content;
};
// Complex components
const AdvancedPlugin: StatusBarComponent = () => {
const content = useEditorContent();
const wordCount = useMemo(() =>
content.trim().split(/\s+/).filter(Boolean).length,
[content]
);
return React.createElement('div', null, `${wordCount} words`);
};
Plugin Communication
Plugins can communicate with each other through the event system:
// Publishing events
lokus.events.emit('plugin.word-count.updated', { count: wordCount });
// Subscribing to events
lokus.events.on('plugin.*.updated', (event, data) => {
console.log(`Plugin ${event.source} updated:`, data);
});
// Direct plugin communication
const otherPlugin = lokus.plugins.get('other-plugin-id');
if (otherPlugin && otherPlugin.api) {
otherPlugin.api.doSomething();
}
File System Access
Plugins can access files through the secure API:
// Read file content
const content = await lokus.fs.readFile('/path/to/file.md');
// Write file content
await lokus.fs.writeFile('/path/to/output.md', processedContent);
// Watch for file changes
const watcher = lokus.fs.watch('/workspace/folder', (event, filename) => {
console.log(`File ${filename} ${event}`);
});
// List directory contents
const files = await lokus.fs.readDir('/workspace');
Editor Integration
Interact with the editor programmatically:
// Get current content
const content = lokus.editor.getContent();
// Set content
lokus.editor.setContent('New content here');
// Insert at cursor
lokus.editor.insertAtCursor('Inserted text');
// Get selection
const selection = lokus.editor.getSelection();
// Register editor commands
lokus.editor.addCommand('my-command', () => {
lokus.editor.insertAtCursor('Hello from plugin!');
});
Plugin Examples
Code Snippets Plugin
interface Snippet {
id: string;
name: string;
language: string;
code: string;
description?: string;
}
const CodeSnippetsPlugin: LokusPlugin = {
id: 'code-snippets',
name: 'Code Snippets',
version: '1.0.0',
activate(context) {
// Load snippets from storage
const snippets: Snippet[] = context.storage.get('snippets', []);
// Register command palette command
context.commands.register('insert-snippet', {
title: 'Insert Code Snippet',
async execute() {
const selected = await lokus.ui.showQuickPick(
snippets.map(s => ({
label: s.name,
description: s.description,
detail: s.language,
value: s
}))
);
if (selected) {
lokus.editor.insertAtCursor(`\`\`\`${selected.language}\n${selected.code}\n\`\`\``);
}
}
});
// Status bar component
const SnippetsButton: StatusBarComponent = () => {
return React.createElement('button', {
onClick: () => context.commands.execute('insert-snippet'),
style: {
background: 'none',
border: 'none',
color: 'var(--lokus-text)',
cursor: 'pointer',
fontSize: '12px'
}
}, `${snippets.length} snippets`);
};
context.statusBar.register('snippets', SnippetsButton);
}
};
Custom Theme Plugin
const CustomThemePlugin: LokusPlugin = {
id: 'custom-theme',
name: 'Custom Theme Manager',
version: '1.0.0',
activate(context) {
const themes = new Map<string, any>();
// Register theme
const registerTheme = (id: string, theme: any) => {
themes.set(id, theme);
lokus.themes.register(id, theme);
};
// Built-in themes
registerTheme('ocean-blue', {
name: 'Ocean Blue',
colors: {
primary: '#0066cc',
secondary: '#004499',
background: '#f0f8ff',
text: '#333333'
}
});
// Theme selector component
const ThemeSelector: StatusBarComponent = () => {
const [currentTheme, setCurrentTheme] = React.useState(
lokus.themes.getCurrent()
);
const cycleTheme = () => {
const themeIds = Array.from(themes.keys());
const currentIndex = themeIds.indexOf(currentTheme);
const nextIndex = (currentIndex + 1) % themeIds.length;
const nextTheme = themeIds[nextIndex];
lokus.themes.apply(nextTheme);
setCurrentTheme(nextTheme);
};
return React.createElement('button', {
onClick: cycleTheme,
style: {
background: 'none',
border: 'none',
color: 'var(--lokus-text)',
cursor: 'pointer'
}
}, 'Theme');
};
context.statusBar.register('theme-selector', ThemeSelector);
}
};
Testing Plugins
Unit Testing
// tests/plugin.test.ts
import { describe, it, expect, vi } from 'vitest';
import wordCountPlugin from '../src/index';
describe('Word Count Plugin', () => {
it('should register status bar component', () => {
const mockContext = {
statusBar: {
register: vi.fn()
}
};
wordCountPlugin.activate(mockContext);
expect(mockContext.statusBar.register).toHaveBeenCalledWith(
'word-count',
expect.any(Function),
expect.objectContaining({
position: 'right'
})
);
});
it('should count words correctly', () => {
const testText = 'Hello world this is a test';
const expectedWords = 6;
// Test word counting logic
const words = testText.trim().split(/\s+/).filter(Boolean).length;
expect(words).toBe(expectedWords);
});
});
Integration Testing
// tests/integration.test.ts
import { describe, it, expect } from 'vitest';
import { createTestEnvironment } from '@lokus/plugin-testing';
describe('Plugin Integration', () => {
it('should integrate with editor', async () => {
const env = await createTestEnvironment();
await env.loadPlugin('word-count');
// Simulate editor content change
await env.editor.setContent('Hello world');
// Check status bar update
const statusBar = env.statusBar.getComponent('word-count');
expect(statusBar.textContent).toContain('2 words');
});
});
Plugin Distribution
Publishing to NPM
# Build plugin
npm run build
# Publish to NPM
npm publish
# Or publish scoped package
npm publish --access public
Plugin Registry
Register your plugin in the Lokus Plugin Registry:
{
"name": "@your-org/plugin-name",
"lokus": {
"registry": {
"categories": ["productivity", "editor"],
"tags": ["word-count", "statistics"],
"screenshots": ["screenshot1.png", "screenshot2.png"],
"homepage": "https://github.com/your-org/plugin-name",
"repository": "https://github.com/your-org/plugin-name"
}
}
}
Best Practices
Performance
- Lazy Loading: Load heavy dependencies only when needed
- Debouncing: Debounce frequent events like content changes
- Memory Management: Clean up subscriptions and intervals
- Efficient Rendering: Use React.memo for expensive components
Security
- Input Validation: Validate all user inputs and external data
- API Limits: Respect rate limits and resource constraints
- Error Handling: Graceful handling of errors and edge cases
- Permissions: Request only necessary permissions
User Experience
- Progressive Enhancement: Work without breaking core functionality
- Accessibility: Follow accessibility best practices
- Internationalization: Support multiple languages where applicable
- Documentation: Provide clear usage instructions
Development
- TypeScript: Use TypeScript for better development experience
- Testing: Write comprehensive tests for your plugin
- Versioning: Follow semantic versioning for releases
- Code Quality: Use linting and formatting tools
Plugin API Reference
Complete API Documentation: See Plugin API Reference for the complete API documentation.
Example Plugins: Check out Plugin Examples for more comprehensive examples.
Plugin Testing: Learn about Plugin Testing for thorough testing strategies.