mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-24 11:14:23 +01:00
feat: convert attachments to text on delete (#1863)
Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Dax <mail@thdxr.com>
This commit is contained in:
committed by
opencode
parent
036b24791d
commit
a4c14dbb2d
@@ -670,6 +670,28 @@ func (m *Model) InsertAttachment(att *attachment.Attachment) {
|
||||
m.SetCursorColumn(m.col)
|
||||
}
|
||||
|
||||
// removeAttachmentAtCursor replaces the attachment at or immediately before the
|
||||
// cursor with its textual display and positions the cursor at the end of the
|
||||
// inserted text. Returns true if an attachment was removed.
|
||||
func (m *Model) removeAttachmentAtCursor() bool {
|
||||
att, startIdx, _ := m.isAttachmentAtCursor()
|
||||
if att == nil {
|
||||
return false
|
||||
}
|
||||
// Replace the attachment element with the display runes
|
||||
before := m.value[m.row][:startIdx]
|
||||
after := m.value[m.row][startIdx+1:]
|
||||
replacement := runesToInterfaces([]rune(att.Display))
|
||||
newRow := make([]any, 0, len(before)+len(replacement)+len(after))
|
||||
newRow = append(newRow, before...)
|
||||
newRow = append(newRow, replacement...)
|
||||
newRow = append(newRow, after...)
|
||||
m.value[m.row] = newRow
|
||||
m.col = startIdx + len(replacement)
|
||||
m.SetCursorColumn(m.col)
|
||||
return true
|
||||
}
|
||||
|
||||
// ReplaceRange replaces text from startCol to endCol on the current row with the given string.
|
||||
// This preserves attachments outside the replaced range.
|
||||
func (m *Model) ReplaceRange(startCol, endCol int, replacement string) {
|
||||
@@ -1577,6 +1599,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
}
|
||||
m.deleteBeforeCursor()
|
||||
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
|
||||
// If the cursor is at or just after an attachment, convert it to text instead of deleting
|
||||
if att, _, _ := m.isAttachmentAtCursor(); att != nil {
|
||||
if m.removeAttachmentAtCursor() {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
||||
if m.col <= 0 {
|
||||
m.mergeLineAbove(m.row)
|
||||
@@ -1587,6 +1615,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
m.SetCursorColumn(m.col - 1)
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
|
||||
// If the cursor is on an attachment, convert it to text instead of deleting
|
||||
if att, _, _ := m.isAttachmentAtCursor(); att != nil {
|
||||
if m.removeAttachmentAtCursor() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(m.value[m.row]) > 0 && m.col < len(m.value[m.row]) {
|
||||
m.value[m.row] = slices.Delete(m.value[m.row], m.col, m.col+1)
|
||||
}
|
||||
|
||||
75
packages/tui/internal/components/textarea/textarea_test.go
Normal file
75
packages/tui/internal/components/textarea/textarea_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package textarea
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sst/opencode/internal/attachment"
|
||||
)
|
||||
|
||||
func TestRemoveAttachmentAtCursor_ConvertsToText_WhenCursorAfterAttachment(t *testing.T) {
|
||||
m := New()
|
||||
m.InsertString("a ")
|
||||
att := &attachment.Attachment{ID: "1", Display: "@file.txt"}
|
||||
m.InsertAttachment(att)
|
||||
m.InsertString(" b")
|
||||
|
||||
// Position cursor immediately after the attachment (index 3: 'a',' ',att,' ', 'b')
|
||||
m.SetCursorColumn(3)
|
||||
|
||||
if ok := m.removeAttachmentAtCursor(); !ok {
|
||||
t.Fatalf("expected removal to occur")
|
||||
}
|
||||
got := m.Value()
|
||||
want := "a @file.txt b"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAttachmentAtCursor_ConvertsToText_WhenCursorOnAttachment(t *testing.T) {
|
||||
m := New()
|
||||
m.InsertString("x ")
|
||||
att := &attachment.Attachment{ID: "2", Display: "@img.png"}
|
||||
m.InsertAttachment(att)
|
||||
m.InsertString(" y")
|
||||
|
||||
// Position cursor on the attachment token (index 2: 'x',' ',att,' ', 'y')
|
||||
m.SetCursorColumn(2)
|
||||
|
||||
if ok := m.removeAttachmentAtCursor(); !ok {
|
||||
t.Fatalf("expected removal to occur")
|
||||
}
|
||||
got := m.Value()
|
||||
want := "x @img.png y"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAttachmentAtCursor_StartOfLine(t *testing.T) {
|
||||
m := New()
|
||||
att := &attachment.Attachment{ID: "3", Display: "@a.txt"}
|
||||
m.InsertAttachment(att)
|
||||
m.InsertString(" tail")
|
||||
|
||||
// Position cursor immediately after the attachment at start of line (index 1)
|
||||
m.SetCursorColumn(1)
|
||||
if ok := m.removeAttachmentAtCursor(); !ok {
|
||||
t.Fatalf("expected removal to occur at start of line")
|
||||
}
|
||||
if got := m.Value(); got != "@a.txt tail" {
|
||||
t.Fatalf("unexpected value: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAttachmentAtCursor_NoAttachment_NoChange(t *testing.T) {
|
||||
m := New()
|
||||
m.InsertString("hello world")
|
||||
col := m.CursorColumn()
|
||||
if ok := m.removeAttachmentAtCursor(); ok {
|
||||
t.Fatalf("did not expect removal to occur")
|
||||
}
|
||||
if m.Value() != "hello world" || m.CursorColumn() != col {
|
||||
t.Fatalf("value or cursor unexpectedly changed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user