Skip to content

Add region mode (for inline TUIs)#114

Closed
adsr wants to merge 1 commit intomasterfrom
region-mode
Closed

Add region mode (for inline TUIs)#114
adsr wants to merge 1 commit intomasterfrom
region-mode

Conversation

@adsr
Copy link
Contributor

@adsr adsr commented Sep 7, 2025

Addresses #74

Region mode is a way to use termbox without switching to the alternate screen buffer. In this mode, termbox stays in the normal screen buffer and doesn't clear existing content on the screen.

image

As implemented in this PR, here's how it works. Before initializing, invoke tb_region(N) to request a region of N lines starting at the current cursor line. From there, all coordinates and dimensions passed into and returned from termbox are relative to that region. The region can be expanded or shrunk by calling tb_region(...) again before tb_(poll|peek)_event which will then emit a resize event. The absolute mouse y coord and the real screen height are stored in tb_event.h and tb_event.y respectively if needed. Region mode cannot be toggled on and off once the library is initialized. (That would be possible to implement but more confusing for no practical benefit IMO.)

The argument against adding this is that it adds significant complexity (11 conditionals, new state, a new termcap, new sources of bugs, etc). The argument for adding this is that it unlocks a whole class of TUI applications that presently cannot be developed with termbox.

Points of discussion. Feedback encouraged on all of this per usual.

  • The name "region mode" is what made sense to me. Originally I named it "normal mode" (as in the normal screen buffer), but that is probably confusing to most people. "Alternate screen" is a well known concept, but normal / default / main / primary screen doesn't have as well-established a name.
  • The API would be more intuitive as tb_init_region and tb_set_region, but there's a combinatoric problem in that we already have 4 init functions, so that'd be 5 new functions to be consistent (tb_init_region_file, tb_init_region_fd, ...). That said I also really don't really like the vaguely named, dual-purpose tb_region function, or that you can call it before tb_init unlike most other API functions. tb_set_region would be slightly better I think...
  • As implemented in this PR, only full-width regions are supported. To properly support region_x offset and arbitrary region_w, send_clear would get more complicated as we couldn't clear_eos. Should this be included? To avoid back compat issues in the future, it's an option to accept w in tb_region and ignore it for now.
  • I'm not confident in the technique used to force more screen lines. I tried other ways such as sending newlines. That also worked but had the downside of overwriting previous content when expanding the region. indn is less portable. 334 terminfo entries have it vs 1547 for cup for comparison.
  • Parameterized caps like cup are presently hard-coded in termbox. Technically we could implement the parameter mechanism described in terminfo(5) to avoid this, but it's fairy complex. Read the "Parameterized Strings" section. I raise this because this PR adds another hard-coded parameterized cap, indn.
  • So far I only tested this in xterm and vte. If you can, test out demo/region.c, press up, down, pgup, pgdn to see if it works in your terminal.

Notable changes:

  • Add function tb_region for controlling region mode
  • Add cap ed for clearing from cursor to end-of-screen
  • Add hard-coded cap indn for forcing y-scroll (used to
    make room when requested region is larger than what is available)
  • Change update_term_size_via_esc to save and restore cursor (to
    avoid losing original cursor position in region mode)
  • For the above, add hard-coded caps sc and rc (these could be included in code gen but it simplifies the code to include it in the move-and-report macro)
  • demo/keyboard accepts an argument for testing region mode
  • Rename TB_ERR_RESIZE_* to TB_ERR_CURSOR_*
  • Rename TB_RESIZE_FALLBACK_MS to TB_READ_CURSOR_TIMEOUT_MS
  • Add 4 globals screen_(w|h) and region_(y|h)
  • Unify screen sizing routine in update_size
  • Move send_clear out of resize_cellbuf for clarity
  • Reduce code dup with send_cap (will make sep PR)
  • Fix 2 missing if_not_init_return bugs (will make sep PR)

@adsr adsr marked this pull request as draft September 7, 2025 20:29
@adsr adsr requested a review from Copilot September 22, 2025 03:36
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements "region mode" functionality for termbox2, allowing TUI applications to render within a confined region of terminal lines without switching to the alternate screen buffer. This enables inline TUI applications that preserve existing terminal content.

Key changes include:

  • Adding tb_region() function to control region mode behavior
  • Implementing coordinate translation and terminal size management for regions
  • Adding new terminal capabilities for region-specific operations

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
termbox2.h Adds region mode API, new terminal capabilities, and coordinate translation logic
tests/test_region/test.php PHP test script validating region mode resize functionality
tests/test_region/expected.ansi Expected output for region mode test
demo/region.c Simple demo application showcasing region mode with menu selection
demo/keyboard.c Adds region mode testing support via command line argument
codegen.sh Adds ed (clear to end of screen) terminal capability

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

} else if (is_region_mode() && h > 0) {
global.region_h = h;
}
return TB_ERR;
Copy link

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The tb_region() function should return TB_OK on success, not TB_ERR. This will cause all successful calls to appear as failures to the caller.

Suggested change
return TB_ERR;
return TB_OK;

Copilot uses AI. Check for mistakes.
w = tb_wcwidth((wchar_t)back->ch);
}
if (w < 1) w = 1; // wcwidth qreturns -1 for invalid codepoints
if (w < 1) w = 1; // wcwidth returns -1 for invalid codepoints
Copy link

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The comment has a typo: 'qreturns' should be 'returns'.

Copilot uses AI. Check for mistakes.
int rw, rh;
if (sscanf(buf, "\x1b[%d;%dR", &rh, &rw) != 2) {
return TB_ERR_RESIZE_SSCANF;
if (sscanf(buf, "\x1b[%d;%dR", y, x) != 2) { // TODO: HARDCAP
Copy link

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The hardcoded escape sequence \x1b[%d;%dR should be defined as a constant like other terminal capabilities to improve maintainability and consistency.

Copilot uses AI. Check for mistakes.
if (x < 0 || y < 0) {
return TB_OK;
}
if (x < 0 || y < 0) return TB_OK; // TODO: TB_ERR? Is this expected?
Copy link

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The TODO comment indicates uncertainty about the return value for invalid coordinates. This should be resolved - either return TB_ERR for invalid input or document why TB_OK is appropriate.

Suggested change
if (x < 0 || y < 0) return TB_OK; // TODO: TB_ERR? Is this expected?
if (x < 0 || y < 0) return TB_ERR_OUT_OF_BOUNDS;

Copilot uses AI. Check for mistakes.
@cowboyd
Copy link

cowboyd commented Nov 7, 2025

I just learned about termbox2 from the Clay project, and it's exciting to learn that inline TUI is a use case that's being mulled over.

My particular use case for an inline TUI would be fore a testing tool. Essentially, rather than just stream the result to the console, I'd like to have the "region" as you've called it, be interactive. It would show pending status, progress, logs etc... however, once a test result is finalized it would be "committed" to the console and would just be part of the normal output.

In other words, in the tool I'm imagining, the "region" would sit at the bottom of the scroll back history, grow as appropriate, but also shift its offset once lines are "committed" to the history. I suppose one way to think of it would be that in classic terminal UI's, the "region" is fixed at N=1

Is this something you see as being possible with this enhancement?

@adsr
Copy link
Contributor Author

adsr commented Nov 10, 2025

Hi @cowboyd, yes I think what you're describing is possible. However the scrolling behavior may be different across terminals due to how indn is handled. For example demo/region.c in this PR behaves a little differently on the Linux console compared to vte and xterm. I encourage you and others to try it on your terminals.

There may be a more portable way to get the primary buffer to scroll up (to make room for a region that won't fit). IIRC when I wrote this PR I tried with sending newlines instead of indn but it was less reliable.

@adsr
Copy link
Contributor Author

adsr commented Dec 24, 2025

Superseded by #127

@adsr adsr closed this Dec 24, 2025
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.

2 participants