Skip to content

shift+backspace does nothing in Input and TextArea on terminals reporting separate modifiers #6612

Description

@rian-dolphin

I noticed that shift+backspace didn't result in a character being deleted. I made a PR for that one specifically but seems like more might be affected. See quote below.

I used Claude code for investigation and it surfaced this:

In terminals that report the shift modifier as a separate key event (e.g. via the Kitty keyboard protocol, support for which was added in 8.2.7), pressing shift+backspace in an Input or TextArea does nothing. Both widgets only bind plain backspace, so the shift+backspace event matches no binding and is silently dropped.

On terminals that don't report separate modifiers, shift+backspace sends the same bytes as backspace and deletes as expected — so this only affects Kitty-protocol-capable terminals (Kitty, Ghostty, recent WezTerm, Warp). Shift has no distinct meaning for deletion, so the expectation is that it behaves the same as plain backspace, consistent with the alt+backspace (#6593) and super+backspace (#6594) aliases.

The same root cause affects other keys pressed with shift held: shift+delete doesn't forward-delete (Input and TextArea), shift+enter doesn't insert a newline in TextArea (or submit in an Input), and shift+space doesn't insert a space in TextArea — while plain delete, enter, and space all work. shift+delete can be aliased the same way as backspace (add it to the delete,ctrl+d binding). shift+enter and shift+space go through the key-insert path rather than BINDINGS, and shift+enter has the added question of whether it should mean newline or submit.

Confirmed against Textual 8.2.8.

Repro

This headless test fails on main: shift+backspace leaves the text untouched, while plain backspace deletes.

import asyncio
from textual.app import App, ComposeResult
from textual.widgets import TextArea


class A(App):
    def compose(self) -> ComposeResult:
        yield TextArea("Hello")


async def main():
    async with A().run_test() as pilot:
        ta = pilot.app.query_one(TextArea)
        ta.move_cursor((0, 5))
        await pilot.press("shift+backspace")
        print("after shift+backspace:", repr(ta.text))  # -> 'Hello' (unchanged)
        await pilot.press("backspace")
        print("after backspace:", repr(ta.text))         # -> 'Hell'


asyncio.run(main())

Interactively, the same thing happens: on a Kitty-protocol terminal, holding Shift while pressing Backspace in a focused Input/TextArea deletes nothing.

Fix

Bind shift+backspace alongside backspace to delete_left in both widgets. PR: #6611 (scoped to shift+backspace for now; shift+delete would be a small follow-on if wanted).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions