Skip to content

Conversation

@simonklee
Copy link
Contributor

@simonklee simonklee commented Jan 16, 2026

Refactor EditBufferRenderable keyboard shift-selection to track a logical anchor and viewport delta, ensuring the cursor stays visible while selection updates correctly when the viewport scrolls (e.g. Shift+Home/End).

This is a continuation of #522 without FFI plumbing for fixing #521

danolekh and others added 7 commits January 16, 2026 10:47
- remove FFI plumbing
- track logical anchor and viewport-delta selection
- keep backward selection inclusive and clear keyboard selection state on reset
When using Shift+Home/End to select text after scrolling, the viewport
wasn't following the cursor because native code skips ensureCursorVisible when a selection is active. Added
ensureCursorVisibleForSelection() to handle viewport scrolling for keyboard-driven selection.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 16, 2026

@opentui/core

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core@542

@opentui/react

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/react@542

@opentui/solid

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/solid@542

@opentui/core-darwin-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-arm64@542

@opentui/core-darwin-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-darwin-x64@542

@opentui/core-linux-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-arm64@542

@opentui/core-linux-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-linux-x64@542

@opentui/core-win32-arm64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-arm64@542

@opentui/core-win32-x64

npm i https://pkg.pr.new/anomalyco/opentui/@opentui/core-win32-x64@542

commit: 085f6b3

@simonklee
Copy link
Contributor Author

Didn't commit, but used this example to test.

import { CliRenderer, createCliRenderer, TextareaRenderable, BoxRenderable, TextRenderable } from "../index"
import { setupCommonDemoKeys } from "./lib/standalone-keys"

// Content long enough to require scrolling in a small viewport
const initialContent = `Line 1: First line of content
Line 2: Second line
Line 3: Third line
Line 4: Fourth line
Line 5: Fifth line with some longer text to test horizontal behavior
Line 6: Sixth line
Line 7: Seventh line
Line 8: Eighth line
Line 9: Ninth line
Line 10: Tenth line
Line 11: Eleventh line
Line 12: Twelfth line
Line 13: Thirteenth line
Line 14: Fourteenth line
Line 15: Fifteenth line with extra content for testing
Line 16: Sixteenth line
Line 17: Seventeenth line
Line 18: Eighteenth line
Line 19: Nineteenth line
Line 20: Twentieth line - end of content`

let renderer: CliRenderer | null = null
let parentContainer: BoxRenderable | null = null
let editor: TextareaRenderable | null = null
let statusText: TextRenderable | null = null
let instructionsText: TextRenderable | null = null

export async function run(rendererInstance: CliRenderer): Promise<void> {
  renderer = rendererInstance
  renderer.setBackgroundColor("#1a1a2e")

  parentContainer = new BoxRenderable(renderer, {
    id: "parent-container",
    zIndex: 10,
    padding: 1,
    flexDirection: "column",
  })
  renderer.root.add(parentContainer)

  // Instructions at the top
  instructionsText = new TextRenderable(renderer, {
    id: "instructions",
    content: `Issue #521: Scrollable Textarea Selection Bug
Test: Hold Shift + use arrow keys or Cmd+Arrow to select past visible content
Expected: Selection should work correctly even when scrolling
The textarea below has a fixed height of 8 lines (content has 20 lines)`,
    fg: "#f8f8f2",
    height: 4,
    flexShrink: 0,
  })
  parentContainer.add(instructionsText)

  // Fixed-height editor box to force scrolling
  const editorBox = new BoxRenderable(renderer, {
    id: "editor-box",
    borderStyle: "single",
    borderColor: "#6272a4",
    backgroundColor: "#282a36",
    title: "Scrollable Textarea (8 lines visible)",
    titleAlignment: "left",
    border: true,
    height: 10, // 8 lines + 2 for border
    flexShrink: 0,
  })
  parentContainer.add(editorBox)

  // Create scrollable textarea
  editor = new TextareaRenderable(renderer, {
    id: "editor",
    initialValue: initialContent,
    textColor: "#f8f8f2",
    selectionBg: "#44475a",
    selectionFg: "#f8f8f2",
    wrapMode: "none",
    showCursor: true,
    cursorColor: "#50fa7b",
  })
  editorBox.add(editor)

  // Status bar at the bottom
  statusText = new TextRenderable(renderer, {
    id: "status",
    content: "",
    fg: "#8be9fd",
    height: 1,
    flexShrink: 0,
  })
  parentContainer.add(statusText)

  editor.focus()

  rendererInstance.setFrameCallback(async () => {
    if (statusText && editor && !editor.isDestroyed) {
      try {
        const cursor = editor.logicalCursor
        const selection = editor.getSelection()
        const selectionInfo = selection
          ? `Selection: ${selection.start} to ${selection.end} | "${editor.getSelectedText().substring(0, 30)}..."`
          : "No selection"
        statusText.content = `Cursor: Line ${cursor.row + 1}, Col ${cursor.col + 1} | ${selectionInfo}`
      } catch (error) {
        // Ignore errors during shutdown
      }
    }
  })
}

export function destroy(rendererInstance: CliRenderer): void {
  rendererInstance.clearFrameCallbacks()
  parentContainer?.destroy()
  parentContainer = null
  editor = null
  statusText = null
  instructionsText = null
  renderer = null
}

if (import.meta.main) {
  const renderer = await createCliRenderer({
    exitOnCtrlC: true,
    targetFps: 60,
  })
  run(renderer)
  setupCommonDemoKeys(renderer)
}

@kommander
Copy link
Collaborator

Ah sorry for splurting into your PR, should have run the tests fully local first. Will fix.

@kommander
Copy link
Collaborator

I fixed it. I think the keyboard vs. mouse selection distinction could be solved as well, but this seems to satisfy the added expectations for now.

@kommander kommander merged commit f0f7132 into anomalyco:main Jan 18, 2026
6 checks passed
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.

3 participants