flutter_monaco 1.0.0
flutter_monaco: ^1.0.0 copied to clipboard
Integrate Monaco Editor (VS Code's editor) in Flutter apps. Features 100+ languages, syntax highlighting, themes, and full API.
Flutter Monaco #
A Flutter plugin for integrating the Monaco Editor (VS Code's editor) into Flutter applications via WebView.
Features #
- 🎨 Full Monaco Editor - The same editor that powers VS Code
- 🌐 100+ Language Support - Syntax highlighting for all major languages
- 🎭 Multiple Themes - Dark, Light, and High Contrast themes
- 💾 Versioned Asset Caching - Efficient one-time asset installation
- 🖥️ Cross-Platform - Works on Android, iOS, macOS, and Windows
- ⚡ Multiple Editors - Support for unlimited independent editor instances
- 📊 Live Statistics - Real-time line/character counts and selection info
- 🎯 Type-safe API - Comprehensive typed bindings for Monaco's JavaScript API
- 🔍 Find & Replace - Full programmatic find/replace with regex support
- 🎭 Decorations & Markers - Add highlights, errors, warnings to your code
- 📡 Event Streams - Listen to content changes, selection, focus events
⚠️ Platform Support: Currently supports Android, iOS, macOS, and Windows. Web and Linux are not supported at this time.
Screenshots #
Windows | |
![]() |
|
iOS | Android |
![]() |
![]() |
Installation #
Add flutter_monaco
to your pubspec.yaml
:
dependencies:
flutter_monaco: ^<latest version>
Quick Start #
import 'package:flutter/material.dart';
import 'package:flutter_monaco/flutter_monaco.dart';
void main() {
runApp(
MaterialApp(
title: 'Flutter Monaco',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: Scaffold(
appBar: AppBar(
title: const Text('Monaco Editor'),
),
body: const MonacoEditor(
showStatusBar: true,
),
),
),
);
}
Type-Safe Enums #
Flutter Monaco provides type-safe enums for all configuration options, preventing runtime errors from invalid string values:
// Languages - 70+ programming languages supported
const EditorOptions(
language: MonacoLanguage.typescript, // Instead of 'typescript'
theme: MonacoTheme.vsDark, // Instead of 'vs-dark'
cursorBlinking: CursorBlinking.smooth,
cursorStyle: CursorStyle.block,
renderWhitespace: RenderWhitespace.boundary,
autoClosingBehavior: AutoClosingBehavior.languageDefined,
);
// Dynamic language selection (when loading from user preferences, etc.)
final language = MonacoLanguage.fromId('python'); // Convert string to enum
await controller.setLanguage(language);
// Theme selection with dynamic conversion
final theme = MonacoTheme.fromId('vs-dark'); // Convert string to enum
await controller.setTheme(theme);
Available Enums #
MonacoTheme
MonacoTheme.vs
- Light themeMonacoTheme.vsDark
- Dark themeMonacoTheme.hcBlack
- High contrast darkMonacoTheme.hcLight
- High contrast light
MonacoLanguage (70+ languages including)
MonacoLanguage.javascript
,MonacoLanguage.typescript
,MonacoLanguage.python
MonacoLanguage.dart
,MonacoLanguage.java
,MonacoLanguage.csharp
MonacoLanguage.go
,MonacoLanguage.rust
,MonacoLanguage.swift
MonacoLanguage.html
,MonacoLanguage.css
,MonacoLanguage.scss
MonacoLanguage.json
,MonacoLanguage.yaml
,MonacoLanguage.xml
MonacoLanguage.markdown
,MonacoLanguage.sql
,MonacoLanguage.dockerfile
- And many more...
CursorBlinking
CursorBlinking.blink
- Default blinkingCursorBlinking.smooth
- Smooth fade animationCursorBlinking.phase
- Phase animationCursorBlinking.expand
- Expand animationCursorBlinking.solid
- No blinking
CursorStyle
CursorStyle.line
- Vertical line (default)CursorStyle.block
- Block cursorCursorStyle.underline
- Underline cursorCursorStyle.lineThin
- Thin vertical lineCursorStyle.blockOutline
- Outlined blockCursorStyle.underlineThin
- Thin underline
RenderWhitespace
RenderWhitespace.none
- Don't render whitespaceRenderWhitespace.boundary
- Render whitespace at word boundariesRenderWhitespace.selection
- Render whitespace in selectionRenderWhitespace.trailing
- Render trailing whitespaceRenderWhitespace.all
- Render all whitespace
AutoClosingBehavior
AutoClosingBehavior.always
- Always auto-close bracketsAutoClosingBehavior.languageDefined
- Use language defaultsAutoClosingBehavior.beforeWhitespace
- Auto-close before whitespaceAutoClosingBehavior.never
- Never auto-close
Multiple Editors Example #
Create a sophisticated multi-editor layout with different languages and themes:
class MultiEditorView extends StatefulWidget {
@override
State<MultiEditorView> createState() => _MultiEditorViewState();
}
class _MultiEditorViewState extends State<MultiEditorView> {
MonacoController? _dartController;
MonacoController? _jsController;
MonacoController? _markdownController;
@override
void initState() {
super.initState();
_initializeEditors();
}
Future<void> _initializeEditors() async {
// Create three independent editors with type-safe configurations
_dartController = await MonacoController.create(
options: const EditorOptions(
language: MonacoLanguage.dart,
theme: MonacoTheme.vsDark,
fontSize: 14,
minimap: true,
),
);
_jsController = await MonacoController.create(
options: const EditorOptions(
language: MonacoLanguage.javascript,
theme: MonacoTheme.vs, // Light theme
fontSize: 14,
minimap: true,
),
);
_markdownController = await MonacoController.create(
options: const EditorOptions(
language: MonacoLanguage.markdown,
theme: MonacoTheme.vsDark,
fontSize: 15,
wordWrap: true,
minimap: false,
lineNumbers: false,
),
);
// Set initial content
await _dartController!.setValue('// Dart code');
await _jsController!.setValue('// JavaScript code');
await _markdownController!.setValue('# Markdown content');
setState(() {});
}
@override
Widget build(BuildContext context) {
if (_dartController == null) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
// Top row - Dart and JavaScript side by side
Expanded(
flex: 2,
child: Row(
children: [
Expanded(child: _dartController!.webViewWidget),
const VerticalDivider(width: 1),
Expanded(child: _jsController!.webViewWidget),
],
),
),
const Divider(height: 1),
// Bottom - Markdown editor
Expanded(
child: _markdownController!.webViewWidget,
),
],
);
}
@override
void dispose() {
_dartController?.dispose();
_jsController?.dispose();
_markdownController?.dispose();
super.dispose();
}
}
API Reference #
MonacoController #
Main controller for interacting with the editor:
// Content management
await controller.setValue('const x = 42;');
String content = await controller.getValue();
// Language and theme (type-safe enums)
await controller.setLanguage(MonacoLanguage.javascript);
await controller.setTheme(MonacoTheme.vsDark);
// Editor actions
await controller.format(); // Format document
await controller.find(); // Open find dialog
await controller.replace(); // Open replace dialog
await controller.selectAll(); // Select all text
await controller.undo(); // Undo last action
await controller.redo(); // Redo last undone action
// Clipboard
await controller.cut();
await controller.copy();
await controller.paste();
// Navigation
await controller.scrollToTop();
await controller.scrollToBottom();
await controller.revealLine(100); // Jump to line 100
await controller.focus(); // Request focus
// Custom actions
await controller.executeAction('editor.action.commentLine');
// Live statistics
controller.liveStats.addListener(() {
final stats = controller.liveStats.value;
print('Lines: ${stats.lineCount.value}');
print('Characters: ${stats.charCount.value}');
print('Selected: ${stats.selectedCharacters.value}');
});
// Event streams
controller.onContentChanged.listen((isFlush) {
print('Content changed (full replace: $isFlush)');
});
controller.onSelectionChanged.listen((range) {
print('Selection: $range');
});
controller.onFocus.listen((_) => print('Editor focused'));
controller.onBlur.listen((_) => print('Editor blurred'));
Advanced Features #
// Find and replace
final matches = await controller.findMatches(
'TODO',
options: const FindOptions(
isRegex: false,
matchCase: true,
wholeWord: true,
),
);
print('Found ${matches.length} TODOs');
// Add error markers
await controller.setErrorMarkers([
MarkerData.error(
range: Range.lines(10, 10),
message: 'Undefined variable',
code: 'E001',
),
]);
// Add decorations (highlights)
await controller.addLineDecorations(
[5, 10, 15], // Line numbers
'highlight-line', // CSS class
);
// Work with multiple models
final uri = await controller.createModel(
'console.log("New file");',
language: MonacoLanguage.javascript,
);
await controller.setModel(uri);
EditorOptions #
Configure the editor appearance and behavior with type-safe enums:
const EditorOptions(
language: MonacoLanguage.javascript,
theme: MonacoTheme.vsDark, // vs, vsDark, hcBlack, hcLight
fontSize: 14,
fontFamily: 'Consolas, monospace',
lineHeight: 1.4,
wordWrap: true, // or false
minimap: false,
lineNumbers: true, // or false
rulers: [80, 120],
tabSize: 2,
insertSpaces: true,
readOnly: false,
automaticLayout: true, // Auto-resize with container
scrollBeyondLastLine: true,
smoothScrolling: false,
cursorBlinking: CursorBlinking.blink, // blink, smooth, phase, expand, solid
cursorStyle: CursorStyle.line, // line, block, underline, lineThin, blockOutline, underlineThin
renderWhitespace: RenderWhitespace.selection, // none, boundary, selection, trailing, all
bracketPairColorization: true,
formatOnPaste: false,
formatOnType: false,
quickSuggestions: true,
parameterHints: true,
hover: true,
contextMenu: true,
mouseWheelZoom: false,
autoClosingBehavior: AutoClosingBehavior.languageDefined, // always, languageDefined, beforeWhitespace, never
);
MonacoAssets #
Manage Monaco Editor assets:
// One-time initialization (called automatically)
await MonacoAssets.ensureReady();
// Get asset information
final info = await MonacoAssets.assetInfo();
print('Monaco cache: ${info['path']}');
print('Total size: ${info['totalSizeMB']} MB');
print('File count: ${info['fileCount']}');
// Clear cache (forces re-extraction on next use)
await MonacoAssets.clearCache();
Architecture #
Asset Management #
The plugin uses a versioned cache system:
- Monaco assets (~30MB) are bundled with the plugin in
assets/monaco/min/
- Assets are extracted once to the app's support directory
- Assets are versioned (e.g.,
monaco-0.52.2/
) for clean updates - Multiple editors share the same asset installation
- Thread-safe initialization with re-entrant protection
Platform Support #
Supported Platforms:
- Android: WebView via
webview_flutter
- iOS: WKWebView with automatic blob worker shim for file:// protocol
- macOS: WKWebView via
webview_flutter
with blob worker shim - Windows: WebView2 via
webview_windows
(requires WebView2 Runtime)
Not Supported:
- Web: Not supported (asset bundling limitations)
- Linux: Not currently supported (WebKitGTK integration pending)
Performance #
- Memory: Each editor instance uses ~30-100MB depending on content
- Startup: First launch extracts assets (one-time ~1-2 seconds)
- Multiple Editors: Tested with 4+ simultaneous editors on desktop
- Workers: Web Workers run in separate threads for syntax highlighting
Requirements #
macOS #
- macOS 10.13 or later
- Xcode (for development)
Windows #
- Windows 10 version 1809 or later
- Microsoft Edge WebView2 Runtime (auto-installed on most Windows 10/11 systems)
Android #
- Android 5.0 (API level 21) or later
- WebView support (included by default)
iOS #
- iOS 11.0 or later
- Info.plist must allow local file access
Example App #
The example directory contains a full demonstration app with:
- Basic single editor setup
- Language and theme switching
- Multi-editor layout (3 editors simultaneously)
- Live statistics display
- Content extraction and manipulation
- Cross-editor content copying
Run the example:
cd example
flutter run -d macos # or android, ios, windows
Important Notes #
Asset Management #
Monaco Editor assets are automatically bundled with this plugin. You do not need to add any assets to your application. The plugin handles:
- Asset extraction on first launch
- Versioned caching for fast subsequent loads
- Automatic cleanup when updating versions
Content Queuing #
The controller automatically queues content and language changes made before the editor is ready. Your content won't be lost even if you call setValue()
immediately after creating the controller.
Event Handling #
For advanced users, you can listen to raw JavaScript events:
controller.onContentChanged.listen((isFlush) { });
controller.onSelectionChanged.listen((range) { });
controller.onFocus.listen((_) { });
controller.onBlur.listen((_) { });
Marker Owners #
When using markers (diagnostics), the clearAllMarkers()
method only clears markers from known owners ('flutter', 'flutter-errors', 'flutter-warnings'). Custom owners must be tracked and cleared separately.
Troubleshooting #
If you are seeing issues where the editor loses keyboard focus after navigating away and back, or after switching apps on macOS/Windows, see the comprehensive guide:
- Focus, First Responder, and Keyboard on Platform Views (macOS/Windows): doc/focus-and-platform-views.md
Desktop Focus Helper (optional) #
For apps that frequently switch routes or windows, you can drop in a tiny helper to reassert focus automatically:
// Once you have a MonacoController instance
MonacoFocusGuard(
controller: controller,
// optionally provide a RouteObserver to re-focus on route return
// routeObserver: myRouteObserver,
);
See the guide above for details and best practices.
Windows: WebView2 not found #
If you get a WebView2 error on Windows, install the WebView2 Runtime: https://developer.microsoft.com/en-us/microsoft-edge/webview2/
macOS/iOS: Workers not loading #
The plugin automatically configures a blob worker shim. If you still have issues:
- Check the console output for errors
- Ensure file:// access is allowed in your WebView configuration
Assets not loading #
If Monaco assets fail to load:
- Check the console for error messages
- Try clearing the cache:
await MonacoAssets.clearCache()
- Ensure your app has file system permissions
Editor not responding #
If the editor becomes unresponsive:
- Check that JavaScript is enabled in the WebView
- Verify the HTML file was generated correctly
- Look for JavaScript errors in the console output
Multiple editors performance #
If performance degrades with multiple editors:
- Limit to 3-4 editors on mobile devices
- Disable minimap for better performance
- Consider lazy initialization of editors
Limitations #
- No Web/Linux Support: The plugin currently only supports Android, iOS, macOS, and Windows.
- Performance: While optimized, running multiple editor instances (4+) can be resource-intensive, especially on older hardware. Each instance runs in a separate WebView, consuming 30-100MB of memory depending on content.
- Startup Time: The first time the app is launched, Monaco's assets (~30MB) are extracted, which can take 1-2 seconds. Subsequent launches are much faster.
- WebView Dependencies: The plugin relies on platform-specific WebView implementations (WebView2 on Windows, WKWebView on Apple platforms). Ensure the target system has the necessary dependencies.
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Support #
If you find this package useful, please consider giving it a star on GitHub and sharing it with the Flutter community.
License #
This plugin is licensed under the MIT License. See LICENSE file for details.
Monaco Editor is licensed under the MIT License by Microsoft.