keymap.js (2494B)
1 import { keymap } from '@codemirror/view'; 2 import { insertNewlineAndIndent } from '@codemirror/commands'; 3 import { 4 adjustHeading, 5 insertNewListItem, 6 isChecklistLine, 7 isHeadingLine, 8 } from './editorUtils.js'; 9 10 function promoteHeading(view) { 11 const line = view.state.doc.lineAt(view.state.selection.main.head); 12 if (!isHeadingLine(line.text)) return false; 13 const next = adjustHeading(line.text, -1); 14 if (next === line.text) return false; 15 view.dispatch({ changes: { from: line.from, to: line.to, insert: next } }); 16 return true; 17 } 18 19 function demoteHeading(view) { 20 const line = view.state.doc.lineAt(view.state.selection.main.head); 21 if (!isHeadingLine(line.text)) return false; 22 const next = adjustHeading(line.text, 1); 23 if (next === line.text) return false; 24 view.dispatch({ changes: { from: line.from, to: line.to, insert: next } }); 25 return true; 26 } 27 28 function continueListItem(view) { 29 const line = view.state.doc.lineAt(view.state.selection.main.head); 30 const prefix = insertNewListItem(line.text); 31 view.dispatch({ 32 changes: { from: line.to, insert: `\n${prefix}` }, 33 selection: { anchor: line.to + 1 + prefix.length }, 34 }); 35 return true; 36 } 37 38 function indentListLine(view) { 39 const line = view.state.doc.lineAt(view.state.selection.main.head); 40 if (!/^(\s*)([-+*]|\d+\.)\s+/.test(line.text) && !isChecklistLine(line.text)) { 41 return false; 42 } 43 view.dispatch({ 44 changes: { from: line.from, to: line.from, insert: ' ' }, 45 selection: { anchor: view.state.selection.main.head + 2 }, 46 }); 47 return true; 48 } 49 50 function handleTab(view) { 51 const line = view.state.doc.lineAt(view.state.selection.main.head); 52 if (isHeadingLine(line.text)) { 53 return demoteHeading(view); 54 } 55 if (indentListLine(view)) return true; 56 return insertNewlineAndIndent(view); 57 } 58 59 function handleShiftTab(view) { 60 const line = view.state.doc.lineAt(view.state.selection.main.head); 61 if (isHeadingLine(line.text)) { 62 return promoteHeading(view); 63 } 64 if (isChecklistLine(line.text) || /^(\s*)([-+*]|\d+\.)\s+/.test(line.text)) { 65 const match = line.text.match(/^(\s*)/); 66 const indent = match?.[1] ?? ''; 67 if (indent.length >= 2) { 68 view.dispatch({ 69 changes: { from: line.from, to: line.to, insert: line.text.slice(2) }, 70 }); 71 return true; 72 } 73 } 74 return false; 75 } 76 77 export const orgKeymap = keymap.of([ 78 { key: 'Tab', run: handleTab }, 79 { key: 'Shift-Tab', run: handleShiftTab }, 80 { key: 'Mod-Enter', run: continueListItem }, 81 ]);