Skip to content

Respect TERM=dumb for cursor position queries - #1183

Merged
kronberger-droid merged 6 commits into
nushell:mainfrom
catlover-bot:fix-term-dumb
Sep 17, 2026
Merged

kronberger-droid merged 6 commits into
nushell:mainfrom
catlover-bot:fix-term-dumb

Conversation

@catlover-bot

@catlover-bot catlover-bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Respect TERM=dumb in Reedline's cursor-position queries and prompt painting.

Painter caches whether TERM=dumb at construction and refreshes the flag on resize. Cursor-position queries, right-prompt layout, and buffer cursor save/restore use this cached flag rather than reading the environment on each paint.

When cursor-position queries are deliberately skipped, anchor_prompt keeps a stale bottom-row anchor without printing an extra newline. Actual query errors retain #1202's newline recovery and stale bottom-row fallback.

Right prompts are suppressed for TERM=dumb. Both small- and large-buffer painting skip SavePosition, and cursor placement skips restoring or repositioning the cursor, leaving it where the last Print put it.

TERM=dumb also disables ANSI coloring without changing the configured with_ansi_colors value. ANSI coloring continues to use the engine's existing TERM-aware policy.

Testing

  • Regression tests for skipped cursor-position queries and anchoring without an extra blank line.
  • Output-capture regression tests for no ESC 7 / ESC 8 in small- and large-buffer painting.
  • Painter tests explicitly set the cached terminal flag instead of depending on the process environment.
  • Full all-features suite under both TERM=xterm-256color and TERM=dumb, using --test-threads=1: 1794 passed, 1 ignored, and 30 doctests passed in each run.
  • cargo clippy --locked --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • git diff --check

Fixes #1009

@fdncred

fdncred commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

I wonder if it's worth centralizing all interactions with the env in calls like std::env::var_os("TERM");? It may be nice to have one function to rule them all since dealing with the env isn't threadsafe. Thoughts?

@catlover-bot

Copy link
Copy Markdown
Contributor Author

That makes sense to me.

I checked the current codebase and found only three direct process environment-variable reads: TERM, USERPROFILE, and HOME, so the scope is small enough to centralize here.

I pushed a follow-up commit that routes those reads through a single utils::environment::var_os helper. I left APIs such as current_dir() out since those are process-state operations rather than environment-variable reads.

One nuance is that the wrapper itself does not make concurrent external environment mutation thread-safe. The benefit is that Reedline now has one place where a future synchronization or snapshot policy can be implemented instead of having environment-variable reads scattered around the crate.

The full library tests and clippy are still green after the refactor.

@fdncred

fdncred commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

The benefit is that Reedline now has one place where a future synchronization or snapshot policy can be implemented instead of having environment-variable reads scattered around the crate.

yup, that's exactly what I was asking.

Thanks for the update. We'll wait to see what @kronberger-droid thinks. I'm not sure it's a regression so I doubt this will be for the upcoming patch.

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Definitely not a regression, thats a longstanding issue.
I would not rush it in before the patch.

I will take a closer look tomorrow.

@catlover-bot

Copy link
Copy Markdown
Contributor Author

Thanks!

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Well I think it would make sense to thread in the ansi-coloring removal here to.
It should be easy enough since we already handle it consistently through the config.

@catlover-bot

Copy link
Copy Markdown
Contributor Author

Thanks, I threaded TERM=dumb through the existing ANSI-color configuration as well.

The configured with_ansi_colors value is preserved, while the effective value is disabled when the current TERM is dumb. I compute that effective value once per paint operation, so the same render uses a consistent setting while later runtime changes to TERM are still respected.

I also rebased onto current main and kept the newer W::cursor_position() abstraction, so non-dumb terminals continue through the existing testable terminal-I/O path.

I added coverage for TERM=dumb, a regular terminal, and an unset TERM; the full test suite and clippy are green.

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Tested this in Emacs shell-mode, the hard error is gone, nice!

One problem still remains with the right prompt:

Reedline parks the cursor with ESC 7 / ESC 8 to paint it, which is terminfo sc/rc, and dumb doesn't have either.

They land in the buffer literally and CurrentDateTime gets inlined into the prompt line:

~/Projects/rust/reedline> ^[708/26/2026 03:35:09 PM^[8^[7^[8hello world

I would also just skip the right prompt since the terminal can't position it anyways.

@catlover-bot

Copy link
Copy Markdown
Contributor Author

Thanks for testing this in Emacs shell-mode. I pushed a follow-up that suppresses the right prompt when TERM=dumb, so the right-prompt paint path no longer emits its cursor save/restore sequence. The TERM read remains dynamic. I added regression coverage for the right-prompt layout; the full suite passes with 1606 tests passed, 1 ignored, plus 30 doctests, and clippy is clean.

Comment thread src/painting/painter.rs Outdated
Comment thread src/engine.rs Outdated
Comment thread src/painting/painter.rs Outdated
@catlover-bot

Copy link
Copy Markdown
Contributor Author

Thanks for testing this in Emacs shell-mode. I now skip the right prompt entirely for TERM=dumb, so Reedline will not emit the save/restore cursor sequences needed to paint it there.

@kronberger-droid kronberger-droid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just those two comments and a rebase and then I think we are ready.

Comment thread src/painting/painter.rs Outdated
Comment thread src/engine.rs Outdated
@catlover-bot

Copy link
Copy Markdown
Contributor Author

Thanks! I addressed both comments and rebased the branch onto the current main. Local tests, clippy, rustfmt, diff checks, and CI are all green.

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Hey there, sorry that I come back with other requests.
But the changes should be small and contained:

Blank line before every prompt.
The query answers Ok(None) on every read_line, so anchor_prompt takes the #1202 fallback each time and prints a \r\n first.
Split the match: Err(_) keeps the print_crlf, Ok(None) only sets the bottom row Stale anchor.

ESC 7 / ESC 8 still lands in the buffer.
The right prompt pair is gone, the buffer paint one is not: SavePosition after before_cursor, RestorePosition in queue_cursor_placement.
Skip both under the same dumb predicate and leave the cursor where the last Print put it.

That is four TERM reads per paint now.
Read it once into a bool on the Painter, at construction and on resize, and take it from there in compute_layout and the print paths.
Then the right prompt tests can set the flag instead of the environment, six of them fail when the suite runs under TERM=dumb.

@catlover-bot

Copy link
Copy Markdown
Contributor Author

Thanks for catching these. I've pushed a follow-up addressing all three points.

anchor_prompt now separates Ok(None) from Err(_): skipping the query keeps the stale bottom-row anchor without emitting a newline, while actual query errors retain #1202's newline recovery.

Both buffer paint paths now skip SavePosition for dumb terminals, and queue_cursor_placement returns without restoring or repositioning the cursor.

Painter caches the dumb-terminal flag at construction and refreshes it on resize. Its cursor queries, right-prompt layout, and buffer print paths use that flag. The affected tests set it explicitly instead of depending on the process environment.

I added output-capture regressions for the extra blank line and the remaining save/restore escapes. The full all-features suite passes under both TERM=xterm-256color and TERM=dumb with --test-threads=1: 1794 passed, 1 ignored, and 30 doctests passed in each run. Clippy, rustfmt, and diff checks also pass.

I also updated the PR description to reflect the cached Painter policy.

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Nice thanks a lot.

@kronberger-droid
kronberger-droid merged commit 7adf024 into nushell:main Sep 17, 2026
7 checks passed
kronberger-droid added a commit that referenced this pull request Sep 17, 2026
The engine read `TERM` on every paint while the painter answers from a
flag cached at construction and on resize, so after a mid-session change
the colors and the skipped escapes could disagree. ANSI coloring now
follows that cache too, thus it no longer tracks `TERM` between resizes.

Follow-up to #1183.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Respect TERM=dumb

3 participants