Add tab indentation and line number toggle to CodeMirror editor
- Wire indentWithTab into keymap (Tab=indent, Shift-Tab=dedent, 4 spaces) - Set indentUnit to 4 spaces - Wrap lineNumbers() in a Compartment for live toggle without editor rebuild - Add Hash toolbar button to toggle line numbers; respects show_line_numbers prop as initial value Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,7 @@ type CMCache = {
|
|||||||
languages?: any;
|
languages?: any;
|
||||||
oneDark?: any;
|
oneDark?: any;
|
||||||
placeholderExt?: any;
|
placeholderExt?: any;
|
||||||
|
Compartment?: any;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
const GLOBAL_KEY = '__cm_singleton_modules_v1';
|
const GLOBAL_KEY = '__cm_singleton_modules_v1';
|
||||||
@@ -135,6 +136,7 @@ export async function ensure_CodeMirror_modules(): Promise<CMCache> {
|
|||||||
EditorState_allowMultipleSelections:
|
EditorState_allowMultipleSelections:
|
||||||
stateMod.EditorState.allowMultipleSelections,
|
stateMod.EditorState.allowMultipleSelections,
|
||||||
EditorState_readOnly: stateMod.EditorState.readOnly,
|
EditorState_readOnly: stateMod.EditorState.readOnly,
|
||||||
|
Compartment: stateMod.Compartment,
|
||||||
|
|
||||||
markdown: markdownMod?.markdown,
|
markdown: markdownMod?.markdown,
|
||||||
markdownLanguage: markdownMod?.markdownLanguage,
|
markdownLanguage: markdownMod?.markdownLanguage,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { ensure_CodeMirror_modules } from './codemirror_modules';
|
|||||||
// import type { key_val } from '$lib/stores/ae_stores';
|
// import type { key_val } from '$lib/stores/ae_stores';
|
||||||
|
|
||||||
// Icons (Standardized to Lucide where possible, or FontAwesome placeholders)
|
// Icons (Standardized to Lucide where possible, or FontAwesome placeholders)
|
||||||
import { Bold, Code, Italic, List } from '@lucide/svelte';
|
import { Bold, Code, Hash, Italic, List } from '@lucide/svelte';
|
||||||
interface Props {
|
interface Props {
|
||||||
content?: string | null;
|
content?: string | null;
|
||||||
new_content?: string;
|
new_content?: string;
|
||||||
@@ -42,6 +42,8 @@ let {
|
|||||||
|
|
||||||
let editor_container: HTMLDivElement | undefined = $state();
|
let editor_container: HTMLDivElement | undefined = $state();
|
||||||
let cm: any = $state(); // CodeMirror modules cache
|
let cm: any = $state(); // CodeMirror modules cache
|
||||||
|
let show_line_nums = $state(untrack(() => show_line_numbers));
|
||||||
|
let ln_compartment: any = null;
|
||||||
|
|
||||||
async function create_editor() {
|
async function create_editor() {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
@@ -55,6 +57,8 @@ async function create_editor() {
|
|||||||
editor_view = null;
|
editor_view = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ln_compartment = new cm.Compartment();
|
||||||
|
|
||||||
const extensions = [
|
const extensions = [
|
||||||
cm.highlightSpecialChars(),
|
cm.highlightSpecialChars(),
|
||||||
cm.history(),
|
cm.history(),
|
||||||
@@ -70,8 +74,9 @@ async function create_editor() {
|
|||||||
cm.highlightActiveLine(),
|
cm.highlightActiveLine(),
|
||||||
cm.highlightActiveLineGutter(),
|
cm.highlightActiveLineGutter(),
|
||||||
|
|
||||||
// Keymaps
|
// Keymaps — indentWithTab must come before defaultKeymap
|
||||||
cm.keymap.of([
|
cm.keymap.of([
|
||||||
|
cm.indentWithTab,
|
||||||
...cm.defaultKeymap,
|
...cm.defaultKeymap,
|
||||||
...cm.searchKeymap,
|
...cm.searchKeymap,
|
||||||
...cm.historyKeymap,
|
...cm.historyKeymap,
|
||||||
@@ -80,6 +85,9 @@ async function create_editor() {
|
|||||||
...cm.lintKeymap
|
...cm.lintKeymap
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
// 4-space indentation unit
|
||||||
|
cm.indentUnit.of(' '),
|
||||||
|
|
||||||
// Language Support
|
// Language Support
|
||||||
language === 'markdown'
|
language === 'markdown'
|
||||||
? cm.markdown({ base: cm.markdownLanguage })
|
? cm.markdown({ base: cm.markdownLanguage })
|
||||||
@@ -94,7 +102,7 @@ async function create_editor() {
|
|||||||
readonly
|
readonly
|
||||||
? cm.EditorState.readOnly.of(true)
|
? cm.EditorState.readOnly.of(true)
|
||||||
: cm.EditorView.editable.of(true),
|
: cm.EditorView.editable.of(true),
|
||||||
show_line_numbers ? cm.lineNumbers() : null,
|
ln_compartment.of(show_line_nums ? cm.lineNumbers() : []),
|
||||||
wrap_lines ? cm.EditorView_lineWrapping : null,
|
wrap_lines ? cm.EditorView_lineWrapping : null,
|
||||||
placeholder ? cm.placeholderExt(placeholder) : null,
|
placeholder ? cm.placeholderExt(placeholder) : null,
|
||||||
|
|
||||||
@@ -144,6 +152,14 @@ $effect(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle line numbers without rebuilding the editor
|
||||||
|
$effect(() => {
|
||||||
|
if (!editor_view || !ln_compartment || !cm) return;
|
||||||
|
editor_view.dispatch({
|
||||||
|
effects: ln_compartment.reconfigure(show_line_nums ? cm.lineNumbers() : [])
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// *** Toolbar Helpers
|
// *** Toolbar Helpers
|
||||||
const wrap_selection = (before: string, after: string = before) => {
|
const wrap_selection = (before: string, after: string = before) => {
|
||||||
if (!editor_view) return;
|
if (!editor_view) return;
|
||||||
@@ -244,6 +260,15 @@ const toggle_list = () => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="ml-auto flex gap-1">
|
<div class="ml-auto flex gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm {show_line_nums
|
||||||
|
? 'variant-filled-primary'
|
||||||
|
: 'variant-soft'} hover:variant-filled-primary"
|
||||||
|
onclick={() => (show_line_nums = !show_line_nums)}
|
||||||
|
title="Toggle Line Numbers">
|
||||||
|
<Hash size="14" />
|
||||||
|
</button>
|
||||||
<span
|
<span
|
||||||
class="mr-2 self-center text-[10px] font-bold uppercase opacity-50"
|
class="mr-2 self-center text-[10px] font-bold uppercase opacity-50"
|
||||||
>{language}</span>
|
>{language}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user