diff options
| author | Adam <[email protected]> | 2026-02-20 07:38:05 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-02-20 07:38:22 -0600 |
| commit | 7e681b0bc02b98e932a51e5469bcaeb6649c5f75 (patch) | |
| tree | 8f3beebaa69a9394befe6659dc27b7ffa316eb9b /packages/app/src/components/prompt-input.tsx | |
| parent | 4e9ef3ecc1506c5087511105ac905564d2b0c73f (diff) | |
| download | opencode-7e681b0bc02b98e932a51e5469bcaeb6649c5f75.tar.gz opencode-7e681b0bc02b98e932a51e5469bcaeb6649c5f75.zip | |
fix(app): large text pasted into prompt-input causes main thread lock
Diffstat (limited to 'packages/app/src/components/prompt-input.tsx')
| -rw-r--r-- | packages/app/src/components/prompt-input.tsx | 46 |
1 files changed, 33 insertions, 13 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index b1c608ffc..adfd592f8 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -89,6 +89,8 @@ const EXAMPLES = [ "prompt.example.25", ] as const +const NON_EMPTY_TEXT = /[^\s\u200B]/ + export const PromptInput: Component<PromptInputProps> = (props) => { const sdk = useSDK() const sync = useSync() @@ -636,7 +638,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => { let buffer = "" const flushText = () => { - const content = buffer.replace(/\r\n?/g, "\n").replace(/\u200B/g, "") + let content = buffer + if (content.includes("\r")) content = content.replace(/\r\n?/g, "\n") + if (content.includes("\u200B")) content = content.replace(/\u200B/g, "") buffer = "" if (!content) return parts.push({ type: "text", content, start: position, end: position + content.length }) @@ -714,10 +718,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => { const rawParts = parseFromDOM() const images = imageAttachments() const cursorPosition = getCursorPosition(editorRef) - const rawText = rawParts.map((p) => ("content" in p ? p.content : "")).join("") - const trimmed = rawText.replace(/\u200B/g, "").trim() + const rawText = + rawParts.length === 1 && rawParts[0]?.type === "text" + ? rawParts[0].content + : rawParts.map((p) => ("content" in p ? p.content : "")).join("") const hasNonText = rawParts.some((part) => part.type !== "text") - const shouldReset = trimmed.length === 0 && !hasNonText && images.length === 0 + const shouldReset = !NON_EMPTY_TEXT.test(rawText) && !hasNonText && images.length === 0 if (shouldReset) { closePopover() @@ -757,19 +763,31 @@ export const PromptInput: Component<PromptInputProps> = (props) => { } const addPart = (part: ContentPart) => { + if (part.type === "image") return false + const selection = window.getSelection() - if (!selection || selection.rangeCount === 0) return + if (!selection) return false - const cursorPosition = getCursorPosition(editorRef) - const currentPrompt = prompt.current() - const rawText = currentPrompt.map((p) => ("content" in p ? p.content : "")).join("") - const textBeforeCursor = rawText.substring(0, cursorPosition) - const atMatch = textBeforeCursor.match(/@(\S*)$/) + if (selection.rangeCount === 0 || !editorRef.contains(selection.anchorNode)) { + editorRef.focus() + const cursor = prompt.cursor() ?? promptLength(prompt.current()) + setCursorPosition(editorRef, cursor) + } + + if (selection.rangeCount === 0) return false + const range = selection.getRangeAt(0) + if (!editorRef.contains(range.startContainer)) return false if (part.type === "file" || part.type === "agent") { + const cursorPosition = getCursorPosition(editorRef) + const rawText = prompt + .current() + .map((p) => ("content" in p ? p.content : "")) + .join("") + const textBeforeCursor = rawText.substring(0, cursorPosition) + const atMatch = textBeforeCursor.match(/@(\S*)$/) const pill = createPill(part) const gap = document.createTextNode(" ") - const range = selection.getRangeAt(0) if (atMatch) { const start = atMatch.index ?? cursorPosition - atMatch[0].length @@ -784,8 +802,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => { range.collapse(true) selection.removeAllRanges() selection.addRange(range) - } else if (part.type === "text") { - const range = selection.getRangeAt(0) + } + + if (part.type === "text") { const fragment = createTextFragment(part.content) const last = fragment.lastChild range.deleteContents() @@ -821,6 +840,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { handleInput() closePopover() + return true } const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => { |
