Make tabs flexible in code (#4520)

This commit is contained in:
Gerard Rovira
2023-05-19 16:19:17 +01:00
committed by GitHub
parent 434af9e046
commit 665ed38b64
12 changed files with 108 additions and 225 deletions

View File

@ -18,22 +18,23 @@ import type {
LineBreakNode,
SerializedElementNode,
SerializedTabNode,
TabNode,
} from 'lexical';
import {ElementNode, TextNode, TabNode} from 'lexical';
import {ElementNode, TextNode} from 'lexical';
/**
* CodeHighlighter
*/
declare export function getEndOfCodeInLine(
anchor: CodeHighlightNode | CodeTabNode,
): CodeHighlightNode | CodeTabNode;
anchor: CodeHighlightNode | TabNode,
): CodeHighlightNode | TabNode;
declare export function getStartOfCodeInLine(
anchor: CodeHighlightNode | CodeTabNode,
anchor: CodeHighlightNode | TabNode,
offset: number,
): null | {
node: CodeHighlightNode | CodeTabNode | LineBreakNode,
node: CodeHighlightNode | TabNode | LineBreakNode,
offset: number,
};
@ -91,14 +92,14 @@ declare export var getCodeLanguages: () => Array<string>;
declare export var getDefaultCodeLanguage: () => string;
declare export function getFirstCodeNodeOfLine(
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
): null | CodeHighlightNode | CodeTabNode | LineBreakNode;
anchor: CodeHighlightNode | TabNode | LineBreakNode,
): null | CodeHighlightNode | TabNode | LineBreakNode;
declare export function getLanguageFriendlyName(lang: string): string;
declare export function getLastCodeNodeOfLine(
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
): CodeHighlightNode | CodeTabNode | LineBreakNode;
anchor: CodeHighlightNode | TabNode | LineBreakNode,
): CodeHighlightNode | TabNode | LineBreakNode;
declare export function normalizeCodeLang(lang: string): string;
@ -127,34 +128,8 @@ declare export class CodeNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
restoreSelection?: boolean,
): null | ParagraphNode | CodeHighlightNode | CodeTabNode;
): null | ParagraphNode | CodeHighlightNode | TabNode;
collapseAtStart(): true;
setLanguage(language: string): void;
getLanguage(): string | void;
}
/**
* CodeTabNode
*/
export type SerializedCodeTabNode = SerializedTabNode;
declare export function $createCodeTabNode(): CodeTabNode;
declare export function $isCodeTabNode(
node: LexicalNode | null | void,
): boolean %checks(node instanceof CodeTabNode);
declare export class CodeTabNode extends TabNode {
static getType(): string;
// $FlowFixMe
static clone(node: CodeTabNode): CodeTabNode;
static importJSON(_serializedTabNode: SerializedCodeTabNode): CodeTabNode;
exportJSON(): SerializedTabNode;
createDOM(config: EditorConfig): HTMLElement;
updateDOM(
prevNode: TextNode,
dom: HTMLElement,
config: EditorConfig,
): boolean;
}

View File

@ -14,6 +14,7 @@ import type {
NodeKey,
SerializedTextNode,
Spread,
TabNode,
} from 'lexical';
import 'prismjs/components/prism-clike';
@ -35,11 +36,15 @@ import {
addClassNamesToElement,
removeClassNamesFromElement,
} from '@lexical/utils';
import {$applyNodeReplacement, ElementNode, TextNode} from 'lexical';
import {
$applyNodeReplacement,
$isTabNode,
ElementNode,
TextNode,
} from 'lexical';
import * as Prism from 'prismjs';
import {$createCodeNode} from './CodeNode';
import {$isCodeTabNode, CodeTabNode} from './CodeTabNode';
export const DEFAULT_CODE_LANGUAGE = 'javascript';
@ -229,11 +234,11 @@ export function $isCodeHighlightNode(
}
export function getFirstCodeNodeOfLine(
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
): null | CodeHighlightNode | CodeTabNode | LineBreakNode {
anchor: CodeHighlightNode | TabNode | LineBreakNode,
): null | CodeHighlightNode | TabNode | LineBreakNode {
let previousNode = anchor;
let node: null | LexicalNode = anchor;
while ($isCodeHighlightNode(node) || $isCodeTabNode(node)) {
while ($isCodeHighlightNode(node) || $isTabNode(node)) {
previousNode = node;
node = node.getPreviousSibling();
}
@ -241,11 +246,11 @@ export function getFirstCodeNodeOfLine(
}
export function getLastCodeNodeOfLine(
anchor: CodeHighlightNode | CodeTabNode | LineBreakNode,
): CodeHighlightNode | CodeTabNode | LineBreakNode {
anchor: CodeHighlightNode | TabNode | LineBreakNode,
): CodeHighlightNode | TabNode | LineBreakNode {
let nextNode = anchor;
let node: null | LexicalNode = anchor;
while ($isCodeHighlightNode(node) || $isCodeTabNode(node)) {
while ($isCodeHighlightNode(node) || $isTabNode(node)) {
nextNode = node;
node = node.getNextSibling();
}

View File

@ -50,13 +50,14 @@ import {
INDENT_CONTENT_COMMAND,
KEY_ARROW_DOWN_COMMAND,
KEY_ARROW_UP_COMMAND,
TabNode,
MOVE_TO_END,
MOVE_TO_START,
$insertNodes,
OUTDENT_CONTENT_COMMAND,
KEY_TAB_COMMAND,
TextNode,
$isTabNode,
TabNode,
} from 'lexical';
import {
@ -70,7 +71,6 @@ import {
import {$isCodeNode, CodeNode} from './CodeNode';
import invariant from 'shared/invariant';
import {CodeTabNode, $createCodeTabNode, $isCodeTabNode} from './CodeTabNode';
type TokenContent = string | Token | (string | Token)[];
@ -95,18 +95,18 @@ export const PrismTokenizer: Tokenizer = {
};
export function getStartOfCodeInLine(
anchor: CodeHighlightNode | CodeTabNode,
anchor: CodeHighlightNode | TabNode,
offset: number,
): null | {
node: CodeHighlightNode | CodeTabNode | LineBreakNode;
node: CodeHighlightNode | TabNode | LineBreakNode;
offset: number;
} {
let last: null | {
node: CodeHighlightNode | CodeTabNode | LineBreakNode;
node: CodeHighlightNode | TabNode | LineBreakNode;
offset: number;
} = null;
let lastNonBlank: null | {node: CodeHighlightNode; offset: number} = null;
let node: null | CodeHighlightNode | CodeTabNode | LineBreakNode = anchor;
let node: null | CodeHighlightNode | TabNode | LineBreakNode = anchor;
let nodeOffset = offset;
let nodeTextContent = anchor.getTextContent();
// eslint-disable-next-line no-constant-condition
@ -118,9 +118,9 @@ export function getStartOfCodeInLine(
}
invariant(
$isCodeHighlightNode(node) ||
$isCodeTabNode(node) ||
$isTabNode(node) ||
$isLineBreakNode(node),
'Expected a valid Code Node: CodeHighlightNode, CodeTabNode, LineBreakNode',
'Expected a valid Code Node: CodeHighlightNode, TabNode, LineBreakNode',
);
if ($isLineBreakNode(node)) {
last = {
@ -208,8 +208,8 @@ function findNextNonBlankInLine(
}
export function getEndOfCodeInLine(
anchor: CodeHighlightNode | CodeTabNode,
): CodeHighlightNode | CodeTabNode {
anchor: CodeHighlightNode | TabNode,
): CodeHighlightNode | TabNode {
const lastNode = getLastCodeNodeOfLine(anchor);
invariant(
!$isLineBreakNode(lastNode),
@ -340,7 +340,7 @@ function getHighlightNodes(tokens: (string | Token)[]): LexicalNode[] {
if (part === '\n' || part === '\r\n') {
nodes.push($createLineBreakNode());
} else if (part === '\t') {
nodes.push($createCodeTabNode());
nodes.push($createTabNode());
} else if (part.length > 0) {
nodes.push($createCodeHighlightNode(part));
}
@ -485,7 +485,7 @@ function isEqual(nodeA: LexicalNode, nodeB: LexicalNode): boolean {
$isCodeHighlightNode(nodeB) &&
nodeA.__text === nodeB.__text &&
nodeA.__highlightType === nodeB.__highlightType) ||
($isCodeTabNode(nodeA) && $isCodeTabNode(nodeB)) ||
($isTabNode(nodeA) && $isTabNode(nodeB)) ||
($isLineBreakNode(nodeA) && $isLineBreakNode(nodeB))
);
}
@ -507,20 +507,18 @@ function $isSelectionInCode(
function $getCodeLines(
selection: RangeSelection,
): Array<Array<CodeHighlightNode | CodeTabNode>> {
): Array<Array<CodeHighlightNode | TabNode>> {
const nodes = selection.getNodes();
const lines: Array<Array<CodeHighlightNode | CodeTabNode>> = [[]];
const lines: Array<Array<CodeHighlightNode | TabNode>> = [[]];
if (nodes.length === 1 && $isCodeNode(nodes[0])) {
return lines;
}
let lastLine: Array<CodeHighlightNode | CodeTabNode> = lines[0];
let lastLine: Array<CodeHighlightNode | TabNode> = lines[0];
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
invariant(
$isCodeHighlightNode(node) ||
$isCodeTabNode(node) ||
$isLineBreakNode(node),
'Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, CodeTabNode and LineBreakNode',
$isCodeHighlightNode(node) || $isTabNode(node) || $isLineBreakNode(node),
'Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, TabNode and LineBreakNode',
);
if ($isLineBreakNode(node)) {
if (i !== 0 && lastLine.length > 0) {
@ -554,9 +552,9 @@ function handleTab(shiftKey: boolean): null | LexicalCommand<void> {
invariant(
$isCodeNode(firstNode) ||
$isCodeHighlightNode(firstNode) ||
$isCodeTabNode(firstNode) ||
$isTabNode(firstNode) ||
$isLineBreakNode(firstNode),
'Expected selection firstNode to be CodeHighlightNode or CodeTabNode',
'Expected selection firstNode to be CodeHighlightNode or TabNode',
);
if ($isCodeNode(firstNode)) {
return indentOrOutdent;
@ -600,19 +598,16 @@ function handleMultilineIndent(type: LexicalCommand<void>): boolean {
for (let i = 0; i < codeLinesLength; i++) {
const line = codeLines[i];
if (line.length > 0) {
let firstOfLine:
| null
| CodeHighlightNode
| CodeTabNode
| LineBreakNode = line[0];
let firstOfLine: null | CodeHighlightNode | TabNode | LineBreakNode =
line[0];
// First and last lines might not be complete
if (i === 0) {
firstOfLine = getFirstCodeNodeOfLine(firstOfLine);
}
if (firstOfLine !== null) {
if (type === INDENT_CONTENT_COMMAND) {
firstOfLine.insertBefore($createCodeTabNode());
} else if ($isCodeTabNode(firstOfLine)) {
firstOfLine.insertBefore($createTabNode());
} else if ($isTabNode(firstOfLine)) {
firstOfLine.remove();
}
}
@ -626,14 +621,14 @@ function handleMultilineIndent(type: LexicalCommand<void>): boolean {
invariant(
$isCodeNode(firstNode) ||
$isCodeHighlightNode(firstNode) ||
$isCodeTabNode(firstNode) ||
$isTabNode(firstNode) ||
$isLineBreakNode(firstNode),
'Expected selection firstNode to be CodeHighlightNode or CodeTabNode',
);
if ($isCodeNode(firstNode)) {
// CodeNode is empty
if (type === INDENT_CONTENT_COMMAND) {
selection.insertNodes([$createCodeTabNode()]);
selection.insertNodes([$createTabNode()]);
}
return true;
}
@ -644,11 +639,11 @@ function handleMultilineIndent(type: LexicalCommand<void>): boolean {
);
if (type === INDENT_CONTENT_COMMAND) {
if ($isLineBreakNode(firstOfLine)) {
firstOfLine.insertAfter($createCodeTabNode());
firstOfLine.insertAfter($createTabNode());
} else {
firstOfLine.insertBefore($createCodeTabNode());
firstOfLine.insertBefore($createTabNode());
}
} else if ($isCodeTabNode(firstOfLine)) {
} else if ($isTabNode(firstOfLine)) {
firstOfLine.remove();
}
return true;
@ -676,8 +671,8 @@ function handleShiftLines(
// Ensure the selection is within the codeblock
if (
!$isSelectionInCode(selection) ||
!($isCodeHighlightNode(anchorNode) || $isCodeTabNode(anchorNode)) ||
!($isCodeHighlightNode(focusNode) || $isCodeTabNode(focusNode))
!($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) ||
!($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))
) {
return false;
}
@ -731,7 +726,7 @@ function handleShiftLines(
const node = range[i];
if (
!$isCodeHighlightNode(node) &&
!$isCodeTabNode(node) &&
!$isTabNode(node) &&
!$isLineBreakNode(node)
) {
return false;
@ -759,7 +754,7 @@ function handleShiftLines(
const maybeInsertionPoint =
$isCodeHighlightNode(sibling) ||
$isCodeTabNode(sibling) ||
$isTabNode(sibling) ||
$isLineBreakNode(sibling)
? arrowIsUp
? getFirstCodeNodeOfLine(sibling)
@ -801,8 +796,8 @@ function handleMoveTo(
const isMoveToStart = type === MOVE_TO_START;
if (
!($isCodeHighlightNode(anchorNode) || $isCodeTabNode(anchorNode)) ||
!($isCodeHighlightNode(focusNode) || $isCodeTabNode(focusNode))
!($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) ||
!($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))
) {
return false;
}
@ -830,18 +825,6 @@ function handleMoveTo(
return true;
}
function tabNodeTransform(node: TabNode): void {
if ($isCodeNode(node.getParent())) {
node.replace($createCodeTabNode());
}
}
function codeTabNodeTransform(node: CodeTabNode): void {
if (!$isCodeNode(node.getParent())) {
node.replace($createTabNode());
}
}
export function registerCodeHighlighting(
editor: LexicalEditor,
tokenizer?: Tokenizer,
@ -878,12 +861,6 @@ export function registerCodeHighlighting(
editor.registerNodeTransform(CodeHighlightNode, (node) =>
textNodeTransform(node, editor, tokenizer as Tokenizer),
),
editor.registerNodeTransform(TabNode, (node) => {
tabNodeTransform(node);
}),
editor.registerNodeTransform(CodeTabNode, (node) => {
codeTabNodeTransform(node);
}),
editor.registerCommand(
KEY_TAB_COMMAND,
(event) => {
@ -904,7 +881,7 @@ export function registerCodeHighlighting(
if (!$isSelectionInCode(selection)) {
return false;
}
$insertNodes([$createCodeTabNode()]);
$insertNodes([$createTabNode()]);
return true;
},
COMMAND_PRIORITY_LOW,

View File

@ -17,13 +17,9 @@ import type {
RangeSelection,
SerializedElementNode,
Spread,
TabNode,
} from 'lexical';
import type {CodeHighlightNode, CodeTabNode} from '@lexical/code';
import {
$isCodeHighlightNode,
$isCodeTabNode,
$createCodeTabNode,
} from '@lexical/code';
import type {CodeHighlightNode} from '@lexical/code';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';
@ -46,8 +42,11 @@ import {
$createLineBreakNode,
$createParagraphNode,
ElementNode,
$isTabNode,
$createTabNode,
} from 'lexical';
import {
$isCodeHighlightNode,
$createCodeHighlightNode,
getFirstCodeNodeOfLine,
} from './CodeHighlightNode';
@ -222,7 +221,7 @@ export class CodeNode extends ElementNode {
insertNewAfter(
selection: RangeSelection,
restoreSelection = true,
): null | ParagraphNode | CodeHighlightNode | CodeTabNode {
): null | ParagraphNode | CodeHighlightNode | TabNode {
const children = this.getChildren();
const childrenLength = children.length;
@ -250,14 +249,14 @@ export class CodeNode extends ElementNode {
const firstSelectionNode = firstPoint.getNode();
if (
$isCodeHighlightNode(firstSelectionNode) ||
$isCodeTabNode(firstSelectionNode)
$isTabNode(firstSelectionNode)
) {
let node = getFirstCodeNodeOfLine(firstSelectionNode);
const insertNodes = [];
// eslint-disable-next-line no-constant-condition
while (true) {
if ($isCodeTabNode(node)) {
insertNodes.push($createCodeTabNode());
if ($isTabNode(node)) {
insertNodes.push($createTabNode());
node = node.getNextSibling();
} else if ($isCodeHighlightNode(node)) {
let spaces = 0;

View File

@ -1,70 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {
EditorConfig,
LexicalNode,
SerializedTabNode,
TextNode,
} from 'lexical';
import {$applyNodeReplacement, TabNode} from 'lexical';
export type SerializedCodeTabNode = SerializedTabNode;
/** @noInheritDoc */
export class CodeTabNode extends TabNode {
static getType(): string {
return 'code-tab';
}
static clone(node: CodeTabNode): CodeTabNode {
return new CodeTabNode(node.__key);
}
static importJSON(_serializedTabNode: SerializedCodeTabNode): CodeTabNode {
return $createCodeTabNode();
}
exportJSON(): SerializedTabNode {
return {
...super.exportJSON(),
type: 'code-tab',
version: 1,
};
}
createDOM(config: EditorConfig): HTMLElement {
const span = super.createDOM(config);
// TODO pass through theme
span.style.letterSpacing = '15px';
const text = span.firstChild;
if (text !== null) {
span.replaceChild(document.createTextNode(' '), text);
}
return span;
}
updateDOM(
prevNode: TextNode,
dom: HTMLElement,
config: EditorConfig,
): boolean {
return true;
}
}
export function $createCodeTabNode(): CodeTabNode {
return $applyNodeReplacement(new CodeTabNode());
}
export function $isCodeTabNode(
node: LexicalNode | null | undefined,
): node is CodeTabNode {
return node instanceof CodeTabNode;
}

View File

@ -9,7 +9,6 @@
import {
$createCodeNode,
$isCodeHighlightNode,
$isCodeTabNode,
registerCodeHighlighting,
} from '@lexical/code';
import {registerTabIndentation} from '@lexical/react/LexicalTabIndentationPlugin';
@ -24,6 +23,7 @@ import {
$getSelection,
$isLineBreakNode,
$isRangeSelection,
$isTabNode,
$setSelection,
KEY_ARROW_DOWN_COMMAND,
KEY_ARROW_UP_COMMAND,
@ -180,15 +180,15 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span></code>',
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span></code>',
);
// CodeNode should only render diffs, make sure that the CodeTabNode is not cloned when
// CodeNode should only render diffs, make sure that the TabNode is not cloned when
// appending more text
let tabKey;
await editor.update(() => {
tabKey = $dfs()
.find(({node}) => $isCodeTabNode(node))
.find(({node}) => $isTabNode(node))
.node.getKey();
$getSelection().insertText('foo');
});
@ -198,7 +198,7 @@ describe('LexicalCodeNode tests', () => {
}),
);
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">foo</span></code>',
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">function</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">foo</span></code>',
);
});
@ -221,7 +221,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">f</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span></code>',
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">f</span><span data-lexical-text="true">\t</span></code>',
);
});
@ -244,7 +244,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">function</span></code>',
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
);
await editor.update(() => {
@ -280,7 +280,7 @@ describe('LexicalCodeNode tests', () => {
});
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">function</span></code>',
'<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1"><span data-lexical-text="true">\t</span><span data-lexical-text="true">function</span></code>',
);
await editor.update(() => {
@ -322,13 +322,13 @@ describe('LexicalCodeNode tests', () => {
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span><br><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span></code>`,
2"><span data-lexical-text="true">\t</span><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">\t</span><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></code>`,
);
await editor.dispatchCommand(KEY_TAB_COMMAND, shiftTabKeyboardEvent());
expect(testEnv.innerHTML).toBe(
`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">hello</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">world</span></code>`,
2"><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span><br><span data-lexical-text="true">hello</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">world</span></code>`,
);
});
@ -347,7 +347,7 @@ describe('LexicalCodeNode tests', () => {
await editor.dispatchCommand(KEY_TAB_COMMAND, tabKeyboardEvent());
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">hello</span><br><span style="letter-spacing: 15px;" data-lexical-text="true"> </span></code>`);
2"><span data-lexical-text="true">hello</span><br><span data-lexical-text="true">\t</span></code>`);
});
test('can outdent at arbitrary points in the line (with tabs)', async () => {
@ -390,7 +390,7 @@ describe('LexicalCodeNode tests', () => {
await editor.dispatchCommand(KEY_ARROW_UP_COMMAND, keyEvent);
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
2"><span data-lexical-text="true">ghi</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">jkl</span><br><span data-lexical-text="true">abc</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">def</span></code>`);
2"><span data-lexical-text="true">ghi</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">jkl</span><br><span data-lexical-text="true">abc</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">def</span></code>`);
});
test('code blocks can shift multiple lines (with tab)', async () => {
@ -424,7 +424,7 @@ describe('LexicalCodeNode tests', () => {
expect(testEnv.innerHTML)
.toBe(`<code spellcheck="false" data-highlight-language="javascript" dir="ltr" data-gutter="1
2
3"><span data-lexical-text="true">mno</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">pqr</span><br><span data-lexical-text="true">abc</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">def</span><br><span data-lexical-text="true">ghi</span><span style="letter-spacing: 15px;" data-lexical-text="true"> </span><span data-lexical-text="true">jkl</span></code>`);
3"><span data-lexical-text="true">mno</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">pqr</span><br><span data-lexical-text="true">abc</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">def</span><br><span data-lexical-text="true">ghi</span><span data-lexical-text="true">\t</span><span data-lexical-text="true">jkl</span></code>`);
});
describe('arrows', () => {
@ -493,7 +493,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),
@ -533,7 +533,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),
@ -645,7 +645,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),
@ -690,7 +690,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),
@ -731,7 +731,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),
@ -772,7 +772,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),
@ -818,7 +818,7 @@ describe('LexicalCodeNode tests', () => {
expect(selection.isCollapsed()).toBe(true);
if (moveTo === 'start') {
if (tabOrSpaces === 'tab') {
expect($isCodeTabNode(selection.anchor.getNode())).toBe(true);
expect($isTabNode(selection.anchor.getNode())).toBe(true);
expect(
$isCodeHighlightNode(
selection.anchor.getNode().getNextSibling(),

View File

@ -30,5 +30,3 @@ export {
} from './CodeHighlightNode';
export type {SerializedCodeNode} from './CodeNode';
export {$createCodeNode, $isCodeNode, CodeNode} from './CodeNode';
export type {SerializedCodeTabNode} from './CodeTabNode';
export {$createCodeTabNode, $isCodeTabNode, CodeTabNode} from './CodeTabNode';

View File

@ -361,7 +361,7 @@ test.describe('CodeBlock', () => {
;
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenFunction"
data-lexical-text="true">
@ -388,7 +388,7 @@ test.describe('CodeBlock', () => {
;
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenPunctuation"
data-lexical-text="true">
@ -447,7 +447,7 @@ test.describe('CodeBlock', () => {
{
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenFunction"
data-lexical-text="true">
@ -494,8 +494,8 @@ test.describe('CodeBlock', () => {
spellcheck="false"
data-gutter="123"
data-highlight-language="javascript">
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenAttr"
data-lexical-text="true">
@ -520,9 +520,9 @@ test.describe('CodeBlock', () => {
{
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenFunction"
data-lexical-text="true">
@ -544,8 +544,8 @@ test.describe('CodeBlock', () => {
;
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenPunctuation"
data-lexical-text="true">
@ -567,7 +567,7 @@ test.describe('CodeBlock', () => {
spellcheck="false"
data-gutter="123"
data-highlight-language="javascript">
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenAttr"
data-lexical-text="true">
@ -592,8 +592,8 @@ test.describe('CodeBlock', () => {
{
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenFunction"
data-lexical-text="true">
@ -615,7 +615,7 @@ test.describe('CodeBlock', () => {
;
</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenPunctuation"
data-lexical-text="true">
@ -1091,10 +1091,10 @@ test.describe('CodeBlock', () => {
spellcheck="false"
data-gutter="12"
data-highlight-language="javascript">
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true">a b</span>
<br />
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span data-lexical-text="true">c d</span>
</code>
`,

View File

@ -66,7 +66,7 @@ test.describe('Tab', () => {
spellcheck="false"
data-gutter="1"
data-highlight-language="javascript">
<span style="letter-spacing: 15px" data-lexical-text="true"></span>
<span data-lexical-text="true"></span>
<span
class="PlaygroundEditorTheme__tokenAttr"
data-lexical-text="true">

View File

@ -8,7 +8,7 @@
import type {Klass, LexicalNode} from 'lexical';
import {CodeHighlightNode, CodeNode, CodeTabNode} from '@lexical/code';
import {CodeHighlightNode, CodeNode} from '@lexical/code';
import {HashtagNode} from '@lexical/hashtag';
import {AutoLinkNode, LinkNode} from '@lexical/link';
import {ListItemNode, ListNode} from '@lexical/list';
@ -67,7 +67,6 @@ const PlaygroundNodes: Array<Klass<LexicalNode>> = [
CollapsibleContainerNode,
CollapsibleContentNode,
CollapsibleTitleNode,
CodeTabNode,
];
export default PlaygroundNodes;

View File

@ -101,6 +101,7 @@
margin-bottom: 8px;
overflow-x: auto;
position: relative;
tab-size: 2;
}
.PlaygroundEditorTheme__code:before {
content: attr(data-gutter);

View File

@ -18,7 +18,7 @@ import type {
SerializedTextNode,
} from 'lexical';
import {CodeHighlightNode, CodeNode, CodeTabNode} from '@lexical/code';
import {CodeHighlightNode, CodeNode} from '@lexical/code';
import {HashtagNode} from '@lexical/hashtag';
import {AutoLinkNode, LinkNode} from '@lexical/link';
import {ListItemNode, ListNode} from '@lexical/list';
@ -422,7 +422,6 @@ const DEFAULT_NODES = [
ListItemNode,
QuoteNode,
CodeNode,
CodeTabNode,
TableNode,
TableCellNode,
TableRowNode,