Browse Source

run: refresh prompt layout after paste (#28164)

Pasting into the prompt textarea left its layout stale until the next edit, so the visible content did not reflect the pasted text. Mark the layout dirty on paste and notify the content-change handler once the renderer is idle so the prompt updates immediately.
Simon Klee 5 months ago
parent
commit
1124315267
1 changed files with 44 additions and 2 deletions
  1. 44 2
      packages/opencode/src/cli/cmd/run/footer.prompt.tsx

+ 44 - 2
packages/opencode/src/cli/cmd/run/footer.prompt.tsx

@@ -6,8 +6,15 @@
 // while the footer view renders the current menu state below it.
 // while the footer view renders the current menu state below it.
 /** @jsxImportSource @opentui/solid */
 /** @jsxImportSource @opentui/solid */
 import { pathToFileURL } from "bun"
 import { pathToFileURL } from "bun"
-import { StyledText, bg, fg, type KeyBinding, type KeyEvent, type TextareaRenderable } from "@opentui/core"
-import { useKeyboard } from "@opentui/solid"
+import {
+  StyledText,
+  bg,
+  fg,
+  type KeyBinding,
+  type KeyEvent,
+  type TextareaRenderable,
+} from "@opentui/core"
+import { useKeyboard, useRenderer } from "@opentui/solid"
 import fuzzysort from "fuzzysort"
 import fuzzysort from "fuzzysort"
 import path from "path"
 import path from "path"
 import { createEffect, createMemo, createResource, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
 import { createEffect, createMemo, createResource, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
@@ -197,13 +204,45 @@ export function RunPromptBody(props: {
   onContentChange: () => void
   onContentChange: () => void
   bind: (area?: TextareaRenderable) => void
   bind: (area?: TextareaRenderable) => void
 }) {
 }) {
+  const renderer = useRenderer()
   let area: TextareaRenderable | undefined
   let area: TextareaRenderable | undefined
+  let pasteTick: ReturnType<typeof setTimeout> | undefined
+
+  const refreshPasteLayout = () => {
+    if (pasteTick) {
+      clearTimeout(pasteTick)
+    }
+
+    pasteTick = setTimeout(() => {
+      pasteTick = undefined
+      if (!area || area.isDestroyed) {
+        return
+      }
+
+      // Paste can leave the textarea layout stale until the next edit.
+      area.getLayoutNode().markDirty()
+      renderer.requestRender()
+      void renderer
+        .idle()
+        .then(() => {
+          if (!area || area.isDestroyed) {
+            return
+          }
+
+          props.onContentChange()
+        })
+        .catch(() => {})
+    }, 0)
+  }
 
 
   onMount(() => {
   onMount(() => {
     props.bind(area)
     props.bind(area)
   })
   })
 
 
   onCleanup(() => {
   onCleanup(() => {
+    if (pasteTick) {
+      clearTimeout(pasteTick)
+    }
     props.bind(undefined)
     props.bind(undefined)
   })
   })
 
 
@@ -226,6 +265,9 @@ export function RunPromptBody(props: {
           keyBindings={props.bindings()}
           keyBindings={props.bindings()}
           onSubmit={props.onSubmit}
           onSubmit={props.onSubmit}
           onKeyDown={props.onKeyDown}
           onKeyDown={props.onKeyDown}
+          onPaste={() => {
+            refreshPasteLayout()
+          }}
           onContentChange={props.onContentChange}
           onContentChange={props.onContentChange}
           ref={(next) => {
           ref={(next) => {
             area = next
             area = next