[lexical-code-core] Bug Fix: Shift+Tab outdents a code line when the caret is at column 0 - #8992
Closed
LeSingh1 wants to merge 1 commit into
Closed
[lexical-code-core] Bug Fix: Shift+Tab outdents a code line when the caret is at column 0#8992LeSingh1 wants to merge 1 commit into
LeSingh1 wants to merge 1 commit into
Conversation
…caret is at column 0
## Description
`$getCodeLines` deliberately discards a trailing line when the selection ends
exactly at its start:
```ts
// Discard the last line if the selection ends exactly at the
// start of the line (no real selection)
const lastPoint = $createPoint(lastLine[0].getKey(), 0, 'text');
if (!selectionEnd.is(lastPoint)) {
lines.push(lastLine);
}
```
For a **collapsed** caret at column 0 that condition is always true, and the
caret's line is the only line — so `codeLines` comes back empty and control
falls into the collapsed special case, which only ever handles indent:
```ts
if (codeLinesLength === 0 && selection.isCollapsed()) {
if (type === INDENT_CONTENT_COMMAND) {
selection.insertNodes([$createTabNode()]);
}
return true; // OUTDENT: returns handled, having done nothing
}
```
So Shift+Tab with the caret at the very start of an indented code line silently
did nothing, while the identical keystroke one column to the right outdented
normally. It also returns `true`, so the command is reported as handled and no
lower-priority handler gets a chance.
Column 0 is the position the caret naturally lands on — `Home`, `ArrowUp` from
the line below, or clicking the left gutter — so this is the common case, not
an edge one.
Resolve the caret's own line from the anchor in that branch and apply the same
rule the outdent loop uses: strip a leading `TabNode`, or `tabSize` leading
spaces when the extension is configured for space indentation. An element-typed
anchor (the caret on a blank line) has no line to outdent and still no-ops.
The existing coverage steps around this: `can outdent at arbitrary points in
the line (with tabs)` uses `codeText.select(1, 1)`, and the outdent half of
`can indent/outdent with collapsed selection at start of line (with tabs)`
builds a *non-collapsed* selection spanning the tab, despite its name.
## Test plan
New `packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts`
— a separate file rather than `CodeIndentation.test.ts`, which facebook#8986 is already
editing. This PR is independent of facebook#8986: it touches a different function in
`CodeIndentation.ts` and the two apply cleanly in either order.
Two cases pin the fix; two are controls that pass before and after (outdent
from a non-zero column, and the no-op on an unindented line with no `tabSize`).
### Before
Verified by restoring `CodeIndentation.ts` to its pre-fix contents with the new
tests in place:
```
$ npx vitest run packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts
× outdents when the caret is collapsed at column 0 (on the TabNode) 10ms
× outdents when the caret is collapsed at column 0 of the code text 1ms
✓ still outdents from a non-zero column (unchanged behaviour) 1ms
× strips a space indent from column 0 when tabSize is configured 1ms
✓ is still a no-op on an unindented line 1ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯
AssertionError: expected '\thello' to be 'hello' // Object.is equality
AssertionError: expected '\thello' to be 'hello' // Object.is equality
AssertionError: expected ' hello' to be 'hello' // Object.is equality
Tests 3 failed | 2 passed (5)
```
### After
```
$ npx vitest run packages/lexical-code-core packages/lexical-code packages/lexical-code-prism packages/lexical-code-shiki
Test Files 12 passed (12)
Tests 273 passed | 1 skipped (274)
```
LeSingh1
requested review from
acywatson,
etrepum,
fantactuka,
ivailop7,
potatowagon and
zurfyx
as code owners
August 9, 2026 01:33
|
@LeSingh1 is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
$getCodeLinesdeliberately discards a trailing line when the selection endsexactly at its start:
For a collapsed caret at column 0 that condition is always true, and the
caret's line is the only line — so
codeLinescomes back empty and controlfalls into the collapsed special case, which only ever handles indent:
So Shift+Tab with the caret at the very start of an indented code line silently
did nothing, while the identical keystroke one column to the right outdented
normally. It also returns
true, so the command is reported as handled and nolower-priority handler gets a chance.
Column 0 is the position the caret naturally lands on —
Home,ArrowUpfromthe line below, or clicking the left gutter — so this is the common case, not
an edge one.
Resolve the caret's own line from the anchor in that branch and apply the same
rule the outdent loop uses: strip a leading
TabNode, ortabSizeleadingspaces when the extension is configured for space indentation. An element-typed
anchor (the caret on a blank line) has no line to outdent and still no-ops.
The existing coverage steps around this:
can outdent at arbitrary points in the line (with tabs)usescodeText.select(1, 1), and the outdent half ofcan indent/outdent with collapsed selection at start of line (with tabs)builds a non-collapsed selection spanning the tab, despite its name.
Test plan
New
packages/lexical-code-core/src/__tests__/unit/CodeOutdentCollapsedAtLineStart.test.ts— a separate file rather than
CodeIndentation.test.ts, which #8986 is alreadyediting. This PR is independent of #8986: it touches a different function in
CodeIndentation.tsand the two apply cleanly in either order.Two cases pin the fix; two are controls that pass before and after (outdent
from a non-zero column, and the no-op on an unindented line with no
tabSize).Before
Verified by restoring
CodeIndentation.tsto its pre-fix contents with the newtests in place:
After