diff --git a/pkgs/dartpad_ui/lib/editor/codemirror.dart b/pkgs/dartpad_ui/lib/editor/codemirror.dart index bebf6ffb5..b1587f93c 100644 --- a/pkgs/dartpad_ui/lib/editor/codemirror.dart +++ b/pkgs/dartpad_ui/lib/editor/codemirror.dart @@ -50,6 +50,9 @@ extension type CodeMirror._(JSObject _) implements JSObject { String getTheme() => (getOption('theme') as JSString).toDart; void setTheme(String theme) => setOption('theme', theme.toJS); + String getKeymap() => (getOption('keyMap') as JSString).toDart; + void setKeymap(String keyMap) => setOption('keyMap', keyMap.toJS); + external void scrollTo(num? x, num? y); external ScrollInfo getScrollInfo(); diff --git a/pkgs/dartpad_ui/lib/editor/editor.dart b/pkgs/dartpad_ui/lib/editor/editor.dart index 1278cd2ae..1e4ec9ae1 100644 --- a/pkgs/dartpad_ui/lib/editor/editor.dart +++ b/pkgs/dartpad_ui/lib/editor/editor.dart @@ -228,6 +228,7 @@ class _EditorWidgetState extends State implements EditorService { appModel.sourceCodeController.addListener(_updateCodemirrorFromModel); appModel.analysisIssues .addListener(() => _updateIssues(appModel.analysisIssues.value)); + appModel.vimKeymapsEnabled.addListener(_updateCodemirrorKeymap); widget.appServices.registerEditorService(this); @@ -309,6 +310,7 @@ class _EditorWidgetState extends State implements EditorService { widget.appModel.sourceCodeController .removeListener(_updateCodemirrorFromModel); widget.appModel.appReady.removeListener(_updateEditableStatus); + widget.appModel.vimKeymapsEnabled.removeListener(_updateCodemirrorKeymap); super.dispose(); } @@ -424,6 +426,17 @@ class _EditorWidgetState extends State implements EditorService { ); } } + + void _updateCodemirrorKeymap() { + final enabled = widget.appModel.vimKeymapsEnabled.value; + final cm = codeMirror!; + + if (enabled) { + cm.setKeymap('vim'); + } else { + cm.setKeymap('default'); + } + } } // codemirror commands diff --git a/pkgs/dartpad_ui/lib/main.dart b/pkgs/dartpad_ui/lib/main.dart index 807c9853c..b126fd32e 100644 --- a/pkgs/dartpad_ui/lib/main.dart +++ b/pkgs/dartpad_ui/lib/main.dart @@ -832,7 +832,10 @@ class StatusLineWidget extends StatelessWidget { builder: (context) => MediumDialog( title: 'Keyboard shortcuts', smaller: true, - child: KeyBindingsTable(bindings: keys.keyBindings), + child: KeyBindingsTable( + bindings: keys.keyBindings, + appModel: appModel, + ), ), ), child: Icon( @@ -1157,9 +1160,11 @@ class ContinueInMenu extends StatelessWidget { class KeyBindingsTable extends StatelessWidget { final List<(String, List)> bindings; + final AppModel appModel; const KeyBindingsTable({ required this.bindings, + required this.appModel, super.key, }); @@ -1210,6 +1215,10 @@ class KeyBindingsTable extends StatelessWidget { ], ), ), + const Divider(), + _VimModeSwitch( + appModel: appModel, + ), ], ); } @@ -1283,6 +1292,32 @@ class _BrightnessButton extends StatelessWidget { } } +class _VimModeSwitch extends StatelessWidget { + final AppModel appModel; + + const _VimModeSwitch({ + required this.appModel, + }); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: appModel.vimKeymapsEnabled, + builder: (BuildContext context, bool value, Widget? child) { + return SwitchListTile( + value: value, + title: const Text('Use Vim Key Bindings'), + onChanged: _handleToggle, + ); + }, + ); + } + + void _handleToggle(bool value) { + appModel.vimKeymapsEnabled.value = value; + } +} + extension MenuControllerToggleMenu on MenuController { void toggleMenuState() { if (isOpen) { diff --git a/pkgs/dartpad_ui/lib/model.dart b/pkgs/dartpad_ui/lib/model.dart index d3a09a93f..21eec8511 100644 --- a/pkgs/dartpad_ui/lib/model.dart +++ b/pkgs/dartpad_ui/lib/model.dart @@ -67,6 +67,8 @@ class AppModel { final SplitDragStateManager splitDragStateManager = SplitDragStateManager(); late final StreamSubscription _splitSubscription; + final ValueNotifier vimKeymapsEnabled = ValueNotifier(false); + AppModel() { consoleOutput.addListener(_recalcLayout);