Files
fiddle/public/js/keybindings.js
root 7f51af17a3 Add editor experience features: auto-run, layouts, resizable panels, Emmet, vim/emacs
- Auto-run toggle with localStorage persistence (toolbar checkbox)
- Layout selector: default, top/bottom, editor-only, preview-only
- Resizable panels via drag dividers with double-click reset
- Emmet abbreviation expansion for HTML, CSS, and JSX
- Vim and Emacs keybinding modes with lazy-loaded CDN libraries
- Shared preferences module for localStorage management
- Editor hooks for tab switch and mode change callbacks
2026-02-26 11:19:14 -06:00

110 lines
2.9 KiB
JavaScript

import { getPref, setPref } from './preferences.js';
import { getActiveEditor, setOnTabSwitch, setOnModeChange } from './editors.js';
let currentMode = 'default'; // 'default' | 'vim' | 'emacs'
let activeAdapter = null; // vim or emacs adapter instance
let vimLoaded = false;
let emacsLoaded = false;
const statusBar = () => document.getElementById('vim-status-bar');
function loadScript(src) {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; }
const savedDefine = window.define;
window.define = undefined;
const s = document.createElement('script');
s.src = src;
s.onload = () => { window.define = savedDefine; resolve(); };
s.onerror = () => { window.define = savedDefine; reject(new Error(`Failed to load ${src}`)); };
document.head.appendChild(s);
});
}
async function ensureVim() {
if (vimLoaded) return;
window.monaco = monaco;
await loadScript('https://unpkg.com/monaco-vim@0.4.2/dist/monaco-vim.js');
vimLoaded = true;
}
async function ensureEmacs() {
if (emacsLoaded) return;
window.monaco = monaco;
await loadScript('https://unpkg.com/monaco-emacs/dist/monaco-emacs.js');
emacsLoaded = true;
}
function disposeAdapter() {
if (activeAdapter) {
activeAdapter.dispose();
activeAdapter = null;
}
const bar = statusBar();
if (bar) {
bar.style.display = 'none';
bar.textContent = '';
}
}
async function attachToEditor(editor) {
if (!editor) return;
disposeAdapter();
if (currentMode === 'vim') {
await ensureVim();
const bar = statusBar();
if (bar) bar.style.display = 'block';
activeAdapter = MonacoVim.initVimMode(editor, bar);
} else if (currentMode === 'emacs') {
await ensureEmacs();
const EmacsExtension = MonacoEmacs.EmacsExtension;
activeAdapter = new EmacsExtension(editor);
activeAdapter.start();
}
}
export async function setKeybindingMode(mode) {
disposeAdapter();
currentMode = mode;
setPref('keybindings', mode);
if (mode === 'default') return;
const editor = getActiveEditor();
if (editor) await attachToEditor(editor);
}
export function initKeybindings() {
currentMode = getPref('keybindings') || 'default';
const select = document.getElementById('keybinding-mode');
if (select) {
select.value = currentMode;
select.addEventListener('change', (e) => setKeybindingMode(e.target.value));
}
// Reattach on tab switch
setOnTabSwitch((_tabId, editor) => {
if (currentMode !== 'default' && editor) {
attachToEditor(editor);
}
});
// Reattach on mode change
setOnModeChange(() => {
if (currentMode !== 'default') {
setTimeout(() => {
const editor = getActiveEditor();
if (editor) attachToEditor(editor);
}, 50);
}
});
// Initial attach if non-default
if (currentMode !== 'default') {
const editor = getActiveEditor();
if (editor) attachToEditor(editor);
}
}