summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRon Suhodrev <[email protected]>2025-11-12 03:14:50 +0700
committeropencode <[email protected]>2025-11-11 20:17:36 +0000
commitdc7c5ced4c6cc3643f62268182530cb41132cdfa (patch)
tree593335c12a5d999577e9c447f18530648ff27bb8
parentb8e8fe7e31af0d5460e30459e26fe6df8ca1487a (diff)
downloadopencode-dc7c5ced4c6cc3643f62268182530cb41132cdfa.tar.gz
opencode-dc7c5ced4c6cc3643f62268182530cb41132cdfa.zip
tui: restore full text when editing prompts with summarized content (#4030)
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx81
1 files changed, 73 insertions, 8 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index 4b852e669..408c62cde 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -101,16 +101,81 @@ export function Prompt(props: PromptProps) {
value: "prompt.editor",
onSelect: async (dialog, trigger) => {
dialog.clear()
- const value = trigger === "prompt" ? "" : input.plainText
+
+ // replace summarized text parts with the actual text
+ const text = store.prompt.parts
+ .filter((p) => p.type === "text")
+ .reduce((acc, p) => {
+ if (!p.source) return acc
+ return acc.replace(p.source.text.value, p.text)
+ }, store.prompt.input)
+
+ const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text")
+
+ const value = trigger === "prompt" ? "" : text
const content = await Editor.open({ value, renderer })
- if (content) {
- input.setText(content, { history: false })
- setStore("prompt", {
- input: content,
- parts: [],
+ if (!content) return
+
+ input.setText(content, { history: false })
+
+ // Update positions for nonTextParts based on their location in new content
+ // Filter out parts whose virtual text was deleted
+ // this handles a case where the user edits the text in the editor
+ // such that the virtual text moves around or is deleted
+ const updatedNonTextParts = nonTextParts
+ .map((part) => {
+ let virtualText = ""
+ if (part.type === "file" && part.source?.text) {
+ virtualText = part.source.text.value
+ } else if (part.type === "agent" && part.source) {
+ virtualText = part.source.value
+ }
+
+ if (!virtualText) return part
+
+ const newStart = content.indexOf(virtualText)
+ // if the virtual text is deleted, remove the part
+ if (newStart === -1) return null
+
+ const newEnd = newStart + virtualText.length
+
+ if (part.type === "file" && part.source?.text) {
+ return {
+ ...part,
+ source: {
+ ...part.source,
+ text: {
+ ...part.source.text,
+ start: newStart,
+ end: newEnd,
+ },
+ },
+ }
+ }
+
+ if (part.type === "agent" && part.source) {
+ return {
+ ...part,
+ source: {
+ ...part.source,
+ start: newStart,
+ end: newEnd,
+ },
+ }
+ }
+
+ return part
})
- input.cursorOffset = Bun.stringWidth(content)
- }
+ .filter((part) => part !== null)
+
+ setStore("prompt", {
+ input: content,
+ // keep only the non-text parts because the text parts were
+ // already expanded inline
+ parts: updatedNonTextParts,
+ })
+ restoreExtmarksFromParts(updatedNonTextParts)
+ input.cursorOffset = Bun.stringWidth(content)
},
},
{