feat(code): gate paste auto-collapse behind display.collapse_pastes (#4473)

Add `display.collapse_pastes` (`[ui].collapse_pastes` /
`DEEPAGENTS_CODE_COLLAPSE_PASTES`) to toggle auto-collapsing of large
chat-input pastes.

---

The CLI always collapsed large chat-input pastes into compact `[Pasted
text #N]` placeholders. This adds a `display.collapse_pastes` config
option (env `DEEPAGENTS_CODE_COLLAPSE_PASTES` / `[ui].collapse_pastes`
in `config.toml`, default enabled) so users can turn the behavior off
and have pasted text inserted verbatim. The `ChatInput`/`ChatTextArea`
paste paths now consult the resolved setting before collapsing.

Made by [Open
SWE](https://openswe.vercel.app/agents/e782ab75-8eb0-1100-d501-1f872b11c286)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-07-06 01:10:09 -04:00
committed by GitHub
parent 7b24e149b2
commit ff5dd564a3
5 changed files with 168 additions and 3 deletions
+10
View File
@@ -34,6 +34,16 @@ AUTO_UPDATE = "DEEPAGENTS_CODE_AUTO_UPDATE"
"""Toggle automatic app updates. Enabled by default; set to a falsy value
('0', 'false', 'no', 'off', or empty) to opt out."""
COLLAPSE_PASTES = "DEEPAGENTS_CODE_COLLAPSE_PASTES"
"""Collapse large chat-input pastes into `[Pasted text #N +M lines]` placeholders.
Enabled by default; set to a falsy value (`0`, `false`, `no`, `off`, or empty)
to disable auto-collapsing so pasted text is inserted verbatim. Parsed by
`classify_env_bool` (an unrecognized value falls through to the config value
rather than forcing the default). Also settable via `[ui].collapse_pastes` in
config.toml.
"""
DEBUG = "DEEPAGENTS_CODE_DEBUG"
"""Enable verbose debug logging and preserve the server subprocess log.
@@ -820,6 +820,15 @@ _STATIC_OPTIONS: tuple[ConfigOption, ...] = (
env_var=_env_vars.SHOW_SCROLLBAR,
toml_keys=("ui", "show_scrollbar"),
),
ConfigOption(
key="display.collapse_pastes",
group="Display",
summary="Collapse large chat-input pastes into compact placeholders.",
kind=OptionKind.BOOL,
default=True,
env_var=_env_vars.COLLAPSE_PASTES,
toml_keys=("ui", "collapse_pastes"),
),
ConfigOption(
key="display.hide_cwd",
group="Display",
@@ -151,6 +151,33 @@ def _should_collapse_chat_paste(text: str) -> bool:
return detect_mode_prefix(text) is None and should_collapse_paste(text)
def _load_collapse_pastes() -> bool:
"""Resolve whether large chat-input pastes are collapsed into placeholders.
Reads `DEEPAGENTS_CODE_COLLAPSE_PASTES`, then `[ui].collapse_pastes` in
`~/.deepagents/config.toml`, defaulting to enabled.
Returns:
The resolved preference (defaults to `True`).
"""
from deepagents_code.config_manifest import (
get_option,
load_config_toml,
resolve_scalar,
)
option = get_option("display.collapse_pastes")
if option is None:
# Unreachable unless the manifest key is renamed without updating this
# literal; log so that mismatch surfaces instead of silently defaulting.
logger.warning(
"Unknown config option %r; defaulting to enabled", "display.collapse_pastes"
)
return True
value, _ = resolve_scalar(option, toml_data=load_config_toml())
return bool(value)
class CompletionOption(Static):
"""A clickable completion option in the autocomplete popup."""
@@ -1006,6 +1033,15 @@ class ChatTextArea(TextArea):
return True
return self.text.startswith("/")
def _paste_collapse_enabled(self) -> bool:
"""Return whether large pastes should be collapsed into placeholders.
Reads the owning `ChatInput`'s resolved preference, defaulting to
enabled when the owner is not yet attached.
"""
owner = self._chat_input_owner
return owner is None or owner._collapse_pastes
def _should_start_paste_burst(self, char: str) -> bool:
"""Return whether a keypress should start paste-burst buffering.
@@ -1052,7 +1088,7 @@ class ChatTextArea(TextArea):
self.post_message(self.PastedPaths(payload, parsed.paths))
return
if _should_collapse_chat_paste(payload):
if self._paste_collapse_enabled() and _should_collapse_chat_paste(payload):
self.post_message(self.PastedText(payload))
return
@@ -1387,7 +1423,7 @@ class ChatTextArea(TextArea):
self.post_message(self.PastedPaths(event.text, parsed.paths))
return
if _should_collapse_chat_paste(event.text):
if self._paste_collapse_enabled() and _should_collapse_chat_paste(event.text):
# Intercept the paste so Textual's default _on_paste doesn't insert
# the full text. ChatInput stores the content and inserts a compact
# placeholder instead.
@@ -1658,6 +1694,12 @@ class ChatInput(Vertical):
self._pasted_contents: dict[int, PastedContent] = {}
self._next_paste_id = 1
# Whether large pastes are collapsed into `[Pasted text #N +M lines]`
# placeholders.
# Gated by `display.collapse_pastes` (env / `[ui].collapse_pastes`);
# when disabled, pasted text is inserted verbatim.
self._collapse_pastes = _load_collapse_pastes()
# Guard flag: set True before programmatically stripping the mode
# prefix character so the resulting text-change event does not
# re-evaluate mode.
@@ -2408,7 +2450,7 @@ class ChatInput(Vertical):
parsed = self._parse_dropped_path_payload(pasted)
if parsed is not None:
self._insert_pasted_paths(pasted, parsed.paths)
elif _should_collapse_chat_paste(pasted):
elif self._collapse_pastes and _should_collapse_chat_paste(pasted):
self._collapse_and_insert_paste(pasted)
else:
self._text_area.insert(pasted)
@@ -4351,6 +4351,26 @@ class TestPasteCollapseHelpers:
text = "hello [Pasted text #1] world"
assert expand_paste_refs(text, {}) == text
def test_load_collapse_pastes_default_enabled(self, monkeypatch) -> None:
"""The loader defaults to enabled when nothing overrides it."""
from deepagents_code import config_manifest
from deepagents_code._env_vars import COLLAPSE_PASTES
from deepagents_code.widgets import chat_input
monkeypatch.delenv(COLLAPSE_PASTES, raising=False)
monkeypatch.setattr(config_manifest, "load_config_toml", dict)
assert chat_input._load_collapse_pastes() is True
def test_load_collapse_pastes_env_disables(self, monkeypatch) -> None:
"""A falsy env var disables paste collapsing in the loader."""
from deepagents_code import config_manifest
from deepagents_code._env_vars import COLLAPSE_PASTES
from deepagents_code.widgets import chat_input
monkeypatch.setenv(COLLAPSE_PASTES, "0")
monkeypatch.setattr(config_manifest, "load_config_toml", dict)
assert chat_input._load_collapse_pastes() is False
class TestPasteCollapseIntegration:
"""Integration tests for paste collapsing in ChatInput."""
@@ -4384,6 +4404,64 @@ class TestPasteCollapseIntegration:
assert chat._text_area.text == "short text"
assert len(chat._pasted_contents) == 0
async def test_large_paste_inserted_verbatim_when_disabled(self) -> None:
"""With collapsing disabled, a large paste is inserted in full."""
big_text = "z" * 900
app = _RecordingApp()
async with app.run_test() as pilot:
chat = app.query_one(ChatInput)
assert chat._text_area is not None
chat._collapse_pastes = False
chat.handle_external_paste(big_text)
await pilot.pause()
assert chat._text_area.text == big_text
assert "[Pasted text #1]" not in chat._text_area.text
assert len(chat._pasted_contents) == 0
async def test_bracketed_paste_not_collapsed_when_disabled(self) -> None:
"""With collapsing disabled, `_on_paste` does not collapse the paste.
Exercises the production `_on_paste` path (gated via
`_paste_collapse_enabled()` -> owner `_collapse_pastes`) rather than
`handle_external_paste`, which gates on `_collapse_pastes` directly.
The verbatim insert is left to Textual's base `TextArea._on_paste`
(invoked separately by MRO dispatch), so this only asserts that our
handler took the deferral branch: no placeholder, no stored content.
Were the helper to ignore the owner and collapse anyway, both
assertions would fail.
"""
big_text = "z" * 900
app = _RecordingApp()
async with app.run_test() as pilot:
chat = app.query_one(ChatInput)
assert chat._text_area is not None
chat._collapse_pastes = False
await chat._text_area._on_paste(events.Paste(big_text))
await pilot.pause()
assert "[Pasted text #1]" not in chat._text_area.text
assert len(chat._pasted_contents) == 0
async def test_paste_burst_flush_inserted_verbatim_when_disabled(self) -> None:
"""With collapsing disabled, a buffered paste burst inserts in full."""
big_text = "q" * 900
app = _RecordingApp()
async with app.run_test() as pilot:
chat = app.query_one(ChatInput)
assert chat._text_area is not None
chat._collapse_pastes = False
chat._text_area._paste_burst_buffer = big_text
await chat._text_area._flush_paste_burst()
await pilot.pause()
assert chat._text_area.text == big_text
assert "[Pasted text #1]" not in chat._text_area.text
assert len(chat._pasted_contents) == 0
async def test_multi_line_paste_inserts_placeholder(self) -> None:
"""Pasting text with > 2 newlines inserts a placeholder."""
multi_line = "\n".join(f"line {i}" for i in range(5))
@@ -853,6 +853,32 @@ def test_resolve_bool_env_uses_truthy_semantics(monkeypatch) -> None:
assert resolve_scalar(opt, toml_data={})[0] is False
def test_collapse_pastes_default_enabled() -> None:
"""Paste collapsing is enabled by default when unset."""
opt = get_option("display.collapse_pastes")
assert opt is not None
assert opt.kind is OptionKind.BOOL
assert resolve_scalar(opt, toml_data={}) == (True, "default")
def test_collapse_pastes_env_disables(monkeypatch) -> None:
"""A falsy env var disables paste collapsing."""
opt = get_option("display.collapse_pastes")
assert opt is not None
monkeypatch.setenv(opt.env_var, "0")
assert resolve_scalar(opt, toml_data={})[0] is False
def test_collapse_pastes_toml_disables() -> None:
"""A `[ui].collapse_pastes = false` entry disables paste collapsing."""
opt = get_option("display.collapse_pastes")
assert opt is not None
assert resolve_scalar(opt, toml_data={"ui": {"collapse_pastes": False}}) == (
False,
"config.toml",
)
def test_thread_relative_time_default_matches_runtime_loader() -> None:
"""Fresh thread config shows relative timestamps by default."""
opt = get_option("threads.relative_time")