فهرست منبع

feat(editor): add Emacs markdown-mode keybindings with C-c C-s prefix

Register markdown formatting commands directly in the EmacsHandler using
its native key chain mechanism, following real Emacs markdown-mode conventions:

  C-c C-s b / B  → Bold (**text**)
  C-c C-s i / I  → Italic (*text*)
  C-c C-s c      → Code (`text`)
  C-c C-s s      → Strikethrough (~~text~~)
  C-c C-s p      → Pre/code block (```text```)

This approach:
- Uses 3-stroke key chains processed natively by the emacs plugin
- Avoids conflicts with Emacs navigation keys (C-b, C-f, etc.)
- Follows the Emacs convention of C-c prefix for mode-specific commands
- Keeps emacs-specific code in a dedicated module (emacs.ts)

In emacs mode, standard mod-* formatting shortcuts are excluded from
the editor shortcuts since they conflict with Emacs keybindings.

https://claude.ai/code/session_01UAUcoiWnFEh86tDeug4hCH
Claude 6 روز پیش
والد
کامیت
445cbd9719

+ 74 - 0
packages/editor/src/client/services-internal/keymaps/emacs.ts

@@ -0,0 +1,74 @@
+import type { Extension } from '@codemirror/state';
+import type { EditorView } from '@codemirror/view';
+
+// Toggle markdown symbols around the current selection.
+// If the selection is already wrapped with the symbols, remove them (toggle off).
+const toggleMarkdownSymbol = (
+  view: EditorView,
+  prefix: string,
+  suffix: string,
+): void => {
+  const { from, to, head } = view.state.selection.main;
+  const selectedText = view.state.sliceDoc(from, to);
+
+  let insertText: string;
+  if (selectedText.startsWith(prefix) && selectedText.endsWith(suffix)) {
+    insertText = selectedText.slice(prefix.length, -suffix.length || undefined);
+  } else {
+    insertText = prefix + selectedText + suffix;
+  }
+
+  const selection =
+    from === to
+      ? { anchor: from + prefix.length }
+      : { anchor: from, head: from + insertText.length };
+
+  const transaction = view.state.replaceSelection(insertText);
+  if (head == null) return;
+  view.dispatch(transaction);
+  view.dispatch({ selection });
+  view.focus();
+};
+
+// Register Emacs markdown-mode style commands and keybindings.
+// Uses EmacsHandler.bindKey for 3-stroke key chains (C-c C-s <key>)
+// which are processed natively by the emacs plugin's key chain mechanism.
+const registerMarkdownModeBindings = (
+  EmacsHandler: typeof import('@replit/codemirror-emacs').EmacsHandler,
+): void => {
+  EmacsHandler.addCommands({
+    markdownBold(handler: { view: EditorView }) {
+      toggleMarkdownSymbol(handler.view, '**', '**');
+    },
+    markdownItalic(handler: { view: EditorView }) {
+      toggleMarkdownSymbol(handler.view, '*', '*');
+    },
+    markdownCode(handler: { view: EditorView }) {
+      toggleMarkdownSymbol(handler.view, '`', '`');
+    },
+    markdownStrikethrough(handler: { view: EditorView }) {
+      toggleMarkdownSymbol(handler.view, '~~', '~~');
+    },
+    markdownCodeBlock(handler: { view: EditorView }) {
+      toggleMarkdownSymbol(handler.view, '```\n', '\n```');
+    },
+  });
+
+  // Keybindings following Emacs markdown-mode conventions:
+  //   C-c C-s b / C-c C-s B  → Bold
+  //   C-c C-s i / C-c C-s I  → Italic
+  //   C-c C-s c              → Code (inline)
+  //   C-c C-s s              → Strikethrough
+  //   C-c C-s p              → Pre (code block)
+  EmacsHandler.bindKey('C-c C-s b|C-c C-s S-b', 'markdownBold');
+  EmacsHandler.bindKey('C-c C-s i|C-c C-s S-i', 'markdownItalic');
+  EmacsHandler.bindKey('C-c C-s c', 'markdownCode');
+  EmacsHandler.bindKey('C-c C-s s', 'markdownStrikethrough');
+  EmacsHandler.bindKey('C-c C-s p', 'markdownCodeBlock');
+};
+
+export const emacsKeymap = async (): Promise<Extension> => {
+  const { EmacsHandler, emacs } = await import('@replit/codemirror-emacs');
+  registerMarkdownModeBindings(EmacsHandler);
+  return emacs();
+};

+ 1 - 1
packages/editor/src/client/services-internal/keymaps/index.ts

@@ -11,7 +11,7 @@ export const getKeymap = async (
     case 'vim':
       return (await import('./vim')).vimKeymap(onSave);
     case 'emacs':
-      return (await import('@replit/codemirror-emacs')).emacs();
+      return (await import('./emacs')).emacsKeymap();
     case 'vscode':
       return keymap.of(
         (await import('@replit/codemirror-vscode-keymap')).vscodeKeymap,

+ 20 - 8
packages/editor/src/client/stores/use-editor-shortcuts.ts

@@ -19,6 +19,7 @@ const useKeyBindings = (
   view?: EditorView,
   keymapModeName?: KeyMapMode,
 ): KeyBinding[] => {
+  // Standard formatting keybindings (used for non-emacs modes)
   const makeTextBoldKeyBinding = useMakeTextBoldKeyBinding(
     view,
     keymapModeName,
@@ -27,25 +28,36 @@ const useKeyBindings = (
   const makeTextStrikethroughKeyBinding =
     useMakeTextStrikethroughKeyBinding(view);
   const makeTextCodeCommand = useMakeTextCodeKeyBinding(view);
+
+  // Shared keybindings (no conflicts with any keymap mode)
   const insertNumberedKeyBinding = useInsertNumberedKeyBinding(view);
   const insertBulletListKeyBinding = useInsertBulletListKeyBinding(view);
   const insertBlockquoteKeyBinding = useInsertBlockquoteKeyBinding(view);
-  const InsertLinkKeyBinding = useInsertLinkKeyBinding(view);
+  const insertLinkKeyBinding = useInsertLinkKeyBinding(view);
   const multiCursorKeyBindings = useAddMultiCursorKeyBindings();
 
-  const keyBindings: KeyBinding[] = [
-    makeTextBoldKeyBinding,
-    makeTextItalicKeyBinding,
-    makeTextStrikethroughKeyBinding,
-    makeTextCodeCommand,
+  const sharedKeyBindings: KeyBinding[] = [
     insertNumberedKeyBinding,
     insertBulletListKeyBinding,
     insertBlockquoteKeyBinding,
-    InsertLinkKeyBinding,
+    insertLinkKeyBinding,
     ...multiCursorKeyBindings,
   ];
 
-  return keyBindings;
+  // In emacs mode, formatting keybindings (bold, italic, strikethrough, code)
+  // are registered in the emacs plugin via EmacsHandler.bindKey (C-c C-s prefix).
+  // Exclude them here to avoid conflicts with Emacs navigation keys.
+  if (keymapModeName === 'emacs') {
+    return sharedKeyBindings;
+  }
+
+  return [
+    makeTextBoldKeyBinding,
+    makeTextItalicKeyBinding,
+    makeTextStrikethroughKeyBinding,
+    makeTextCodeCommand,
+    ...sharedKeyBindings,
+  ];
 };
 
 export const useEditorShortcuts = (