[PR #794] [MERGED] fix: preserve terminal's native cursor style by default #1570

Closed
opened 2026-03-14 09:43:50 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/anomalyco/opentui/pull/794
Author: @aarcamp
Created: 3/8/2026
Status: Merged
Merged: 3/9/2026
Merged by: @kommander

Base: mainHead: cursor-style-default


📝 Commits (3)

  • 86a2256 fix: preserve terminal's native cursor style by default
  • 4c83304 test: add cursor default regression
  • b4bee91 documentation

📊 Changes

8 files changed (+66 additions, -6 deletions)

View changed files

packages/core/src/tests/renderer.cursor.test.ts (+26 -0)
📝 packages/core/src/types.ts (+1 -1)
📝 packages/core/src/zig.ts (+2 -2)
📝 packages/core/src/zig/lib.zig (+3 -2)
📝 packages/core/src/zig/renderer.zig (+3 -0)
📝 packages/core/src/zig/terminal.zig (+2 -1)
📝 packages/core/src/zig/tests/renderer_test.zig (+24 -0)
📝 packages/web/src/content/docs/core-concepts/renderer.mdx (+5 -0)

📄 Description

The renderer's cursor style defaulted to .block, which emitted \x1b[2 q on every frame — overriding the user's terminal cursor preference (e.g. bar/line) even when no component explicitly set a cursor style. Add a .default variant to CursorStyle that emits \x1b[0 q (the ANSI "reset cursor style" sequence), and make it the initial state. Components that call setCursorPosition() without setCursorStyle() now preserve the terminal's native cursor.

https://github.com/user-attachments/assets/d7e388d7-d7f3-4e92-96ff-fe8e13da3df6

Standalone script used to produce the demo:

#!/usr/bin/env bun
/**
 * Minimal OpenTUI demo: cursor style default behavior.
 *
 * A custom renderable calls setCursorPosition() without setCursorStyle().
 * This shows what cursor style the renderer uses by default:
 *
 *   Before fix: block cursor (overrides terminal preference)
 *   After fix:  terminal's native cursor style (e.g. bar/line)
 *
 * Controls:
 *   Ctrl-C — quit
 *
 * Run:
 *   bun run scripts/cursor-style-demo.tsx
 */
import React from "react"
import {
  createCliRenderer,
  Renderable,
  type RenderContext,
  type RenderableOptions,
} from "@opentui/core"
import { createRoot, extend } from "@opentui/react"

// A renderable that shows a blinking cursor without setting any style.
// It only calls setCursorPosition — the cursor style comes entirely
// from the renderer's internal default.
class CursorProbe extends Renderable {
  constructor(ctx: RenderContext, options: RenderableOptions) {
    super(ctx, options)
  }

  protected renderSelf(): void {
    this.ctx.setCursorPosition(this.x + 1, this.y + 1, true)
  }
}

extend({ "cursor-probe": CursorProbe })

declare module "@opentui/react" {
  interface OpenTUIComponents {
    "cursor-probe": typeof CursorProbe
  }
}

// ---------------------------------------------------------------------------

const renderer = await createCliRenderer({
  exitOnCtrlC: true,
  useAlternateScreen: true,
})

function App() {
  return (
    <box
      width={60}
      height={10}
      borderStyle="rounded"
      borderColor="cyan"
      title=" OpenTUI cursor default demo "
      titleColor="cyan"
      paddingLeft={1}
      paddingTop={1}
    >
      <box flexDirection="column">
        <text>
          {"This cursor's style is the renderer default.\n" +
           "No component calls setCursorStyle().\n\n" +
           "Before fix: always block\n" +
           "After fix:  terminal's native style"}
        </text>
        <cursor-probe width={1} height={1} />
      </box>
    </box>
  )
}

const root = createRoot(renderer)
root.render(<App />)

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/anomalyco/opentui/pull/794 **Author:** [@aarcamp](https://github.com/aarcamp) **Created:** 3/8/2026 **Status:** ✅ Merged **Merged:** 3/9/2026 **Merged by:** [@kommander](https://github.com/kommander) **Base:** `main` ← **Head:** `cursor-style-default` --- ### 📝 Commits (3) - [`86a2256`](https://github.com/anomalyco/opentui/commit/86a2256403818a56daa8c1dd7579474262f1fe57) fix: preserve terminal's native cursor style by default - [`4c83304`](https://github.com/anomalyco/opentui/commit/4c8330489689f89245d05184c0c1b2b608330f8c) test: add cursor default regression - [`b4bee91`](https://github.com/anomalyco/opentui/commit/b4bee91021e5851529657542f2b29295f4db9250) documentation ### 📊 Changes **8 files changed** (+66 additions, -6 deletions) <details> <summary>View changed files</summary> ➕ `packages/core/src/tests/renderer.cursor.test.ts` (+26 -0) 📝 `packages/core/src/types.ts` (+1 -1) 📝 `packages/core/src/zig.ts` (+2 -2) 📝 `packages/core/src/zig/lib.zig` (+3 -2) 📝 `packages/core/src/zig/renderer.zig` (+3 -0) 📝 `packages/core/src/zig/terminal.zig` (+2 -1) 📝 `packages/core/src/zig/tests/renderer_test.zig` (+24 -0) 📝 `packages/web/src/content/docs/core-concepts/renderer.mdx` (+5 -0) </details> ### 📄 Description The renderer's cursor style defaulted to .block, which emitted \x1b[2 q on every frame — overriding the user's terminal cursor preference (e.g. bar/line) even when no component explicitly set a cursor style. Add a .default variant to CursorStyle that emits \x1b[0 q (the ANSI "reset cursor style" sequence), and make it the initial state. Components that call setCursorPosition() without setCursorStyle() now preserve the terminal's native cursor. https://github.com/user-attachments/assets/d7e388d7-d7f3-4e92-96ff-fe8e13da3df6 Standalone script used to produce the demo: ``` #!/usr/bin/env bun /** * Minimal OpenTUI demo: cursor style default behavior. * * A custom renderable calls setCursorPosition() without setCursorStyle(). * This shows what cursor style the renderer uses by default: * * Before fix: block cursor (overrides terminal preference) * After fix: terminal's native cursor style (e.g. bar/line) * * Controls: * Ctrl-C — quit * * Run: * bun run scripts/cursor-style-demo.tsx */ import React from "react" import { createCliRenderer, Renderable, type RenderContext, type RenderableOptions, } from "@opentui/core" import { createRoot, extend } from "@opentui/react" // A renderable that shows a blinking cursor without setting any style. // It only calls setCursorPosition — the cursor style comes entirely // from the renderer's internal default. class CursorProbe extends Renderable { constructor(ctx: RenderContext, options: RenderableOptions) { super(ctx, options) } protected renderSelf(): void { this.ctx.setCursorPosition(this.x + 1, this.y + 1, true) } } extend({ "cursor-probe": CursorProbe }) declare module "@opentui/react" { interface OpenTUIComponents { "cursor-probe": typeof CursorProbe } } // --------------------------------------------------------------------------- const renderer = await createCliRenderer({ exitOnCtrlC: true, useAlternateScreen: true, }) function App() { return ( <box width={60} height={10} borderStyle="rounded" borderColor="cyan" title=" OpenTUI cursor default demo " titleColor="cyan" paddingLeft={1} paddingTop={1} > <box flexDirection="column"> <text> {"This cursor's style is the renderer default.\n" + "No component calls setCursorStyle().\n\n" + "Before fix: always block\n" + "After fix: terminal's native style"} </text> <cursor-probe width={1} height={1} /> </box> </box> ) } const root = createRoot(renderer) root.render(<App />) ``` --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-14 09:43:50 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#1570
No description provided.