snow-editor

small markdown and org-mode editor
Log | Files | Refs | README

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 ]);