fix: Ctrl-a/e in code fences

This commit is contained in:
Tom Moor
2023-04-16 09:49:19 -04:00
parent aa04a5e6f4
commit f2b3524d87
3 changed files with 141 additions and 41 deletions

View File

@@ -2,6 +2,13 @@ import { NodeType } from "prosemirror-model";
import { EditorState } from "prosemirror-state";
import { Dispatch } from "../types";
/**
* Converts the current node to a paragraph when pressing backspace at the
* beginning of the node and not already a paragraph.
*
* @param type The node type
* @returns A prosemirror command.
*/
export default function backspaceToParagraph(type: NodeType) {
return (state: EditorState, dispatch: Dispatch) => {
const { $from, from, to, empty } = state.selection;

View File

@@ -0,0 +1,122 @@
import { EditorState, TextSelection } from "prosemirror-state";
import isInCode from "../queries/isInCode";
import { Dispatch } from "../types";
/**
* Moves the current selection to the previous newline, this is used inside
* code fences only, prosemirror handles this functionality fine in other nodes.
*
* @returns A prosemirror command.
*/
export const moveToPreviousNewline = (
state: EditorState,
dispatch: Dispatch
) => {
if (!isInCode(state)) {
return false;
}
const $pos = state.selection.$from;
if (!$pos.parent.type.isTextblock) {
return false;
}
// The easiest way to find the previous newline is to reverse the string and
// perform a forward seach as if looking for the next newline.
const beginningOfNode = $pos.pos - $pos.parentOffset;
const startOfLine = $pos.parent.textContent
.split("")
.reverse()
.join("")
.indexOf("\n", $pos.parent.nodeSize - $pos.parentOffset - 2);
if (startOfLine === -1) {
return false;
}
dispatch(
state.tr.setSelection(
TextSelection.create(
state.doc,
beginningOfNode + ($pos.parent.nodeSize - startOfLine - 2)
)
)
);
return true;
};
/**
* Moves the current selection to the next newline, this is used inside code
* fences only, prosemirror handles this functionality fine in other nodes.
*
* @returns A prosemirror command.
*/
export const moveToNextNewline = (state: EditorState, dispatch: Dispatch) => {
if (!isInCode(state)) {
return false;
}
const $pos = state.selection.$to;
if (!$pos.parent.type.isTextblock) {
return false;
}
// find next newline
const beginningOfNode = $pos.pos - $pos.parentOffset;
const endOfLine = $pos.parent.textContent.indexOf("\n", $pos.parentOffset);
if (endOfLine === -1) {
return false;
}
dispatch(
state.tr.setSelection(
TextSelection.create(state.doc, beginningOfNode + endOfLine)
)
);
return true;
};
/**
* Replace the selection with a newline character preceeded by a number of
* spaces to have the new line align with the code on the previous. This is
* standard code editor behavior.
*
* @returns A prosemirror command
*/
export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
if (!isInCode(state)) {
return false;
}
const { tr, selection } = state;
const text = selection?.$anchor?.nodeBefore?.text;
let newText = "\n";
if (text) {
const splitByNewLine = text.split("\n");
const numOfSpaces = splitByNewLine[splitByNewLine.length - 1].search(
/\S|$/
);
newText += " ".repeat(numOfSpaces);
}
dispatch(tr.insertText(newText, selection.from, selection.to));
return true;
};
/**
* Insert two spaces to simulate the tab key.
*
* @returns A prosemirror command
*/
export const insertSpaceTab = (state: EditorState, dispatch: Dispatch) => {
if (!isInCode(state)) {
return false;
}
const { tr, selection } = state;
dispatch(tr.insertText(" ", selection.from, selection.to));
return true;
};

View File

@@ -7,14 +7,7 @@ import {
Schema,
Node as ProsemirrorNode,
} from "prosemirror-model";
import {
EditorState,
Selection,
TextSelection,
Transaction,
Plugin,
PluginKey,
} from "prosemirror-state";
import { Selection, Plugin, PluginKey } from "prosemirror-state";
import refractor from "refractor/core";
import bash from "refractor/lang/bash";
import clike from "refractor/lang/clike";
@@ -56,12 +49,17 @@ import zig from "refractor/lang/zig";
import { Dictionary } from "~/hooks/useDictionary";
import { UserPreferences } from "../../types";
import Storage from "../../utils/Storage";
import {
newlineInCode,
insertSpaceTab,
moveToNextNewline,
moveToPreviousNewline,
} from "../commands/codeFence";
import toggleBlockType from "../commands/toggleBlockType";
import Mermaid from "../extensions/Mermaid";
import Prism, { LANGUAGES } from "../extensions/Prism";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import isInCode from "../queries/isInCode";
import { Dispatch } from "../types";
import Node from "./Node";
const PERSISTENCE_KEY = "rme-code-language";
@@ -228,38 +226,11 @@ export default class CodeFence extends Node {
keys({ type, schema }: { type: NodeType; schema: Schema }) {
return {
"Shift-Ctrl-\\": toggleBlockType(type, schema.nodes.paragraph),
"Shift-Enter": (state: EditorState, dispatch: Dispatch) => {
if (!isInCode(state)) {
return false;
}
const {
tr,
selection,
}: { tr: Transaction; selection: TextSelection } = state;
const text = selection?.$anchor?.nodeBefore?.text;
let newText = "\n";
if (text) {
const splitByNewLine = text.split("\n");
const numOfSpaces = splitByNewLine[splitByNewLine.length - 1].search(
/\S|$/
);
newText += " ".repeat(numOfSpaces);
}
dispatch(tr.insertText(newText, selection.from, selection.to));
return true;
},
Tab: (state: EditorState, dispatch: Dispatch) => {
if (!isInCode(state)) {
return false;
}
const { tr, selection } = state;
dispatch(tr.insertText(" ", selection.from, selection.to));
return true;
},
Tab: insertSpaceTab,
Enter: newlineInCode,
"Shift-Enter": newlineInCode,
"Ctrl-a": moveToPreviousNewline,
"Ctrl-e": moveToNextNewline,
};
}