fix: looks like we resolved the issues with pasting.
All checks were successful
Run Test Suite / test (push) Successful in 46s
All checks were successful
Run Test Suite / test (push) Successful in 46s
We means me and Claude (heavy on the Claude). Originally, if we copied a many line segment into a charwise register, the paste op would error, this is not right, it should paste, just differently.
This commit is contained in:
parent
ffad4f86f6
commit
04c247cc8e
@ -57,29 +57,56 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
{
|
||||
lines := reg.Content
|
||||
|
||||
// Shouldn't happen, just a check
|
||||
if len(lines) != 1 {
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"Charwise register should only have a single line of content."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
if len(lines) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
|
||||
cnt := strings.Repeat(lines[0], max(1, a.Count))
|
||||
curLine := buf.Lines[y]
|
||||
|
||||
// Catch edge cases, end of line, start of blank line
|
||||
insertAt := min(x+1, len(curLine))
|
||||
|
||||
if len(lines) == 1 {
|
||||
// Single-line charwise paste
|
||||
cnt := strings.Repeat(lines[0], max(1, a.Count))
|
||||
newLine := curLine[:insertAt] + cnt + curLine[insertAt:]
|
||||
buf.SetLine(y, newLine)
|
||||
|
||||
win.SetCursorCol(x + len(cnt))
|
||||
} else {
|
||||
// Multi-line charwise paste (e.g., from vi{ yank)
|
||||
suffix := curLine[insertAt:] // Save the part after cursor
|
||||
|
||||
// For count > 1, we paste the content multiple times
|
||||
// Each paste continues from where the previous one ended
|
||||
var content strings.Builder
|
||||
for i := 0; i < a.Count; i++ {
|
||||
for j, line := range lines {
|
||||
if j > 0 {
|
||||
content.WriteString("\n")
|
||||
}
|
||||
content.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
// Split the pasted content into lines
|
||||
pastedLines := strings.Split(content.String(), "\n")
|
||||
|
||||
// First line: append to current line
|
||||
buf.SetLine(y, curLine[:insertAt]+pastedLines[0])
|
||||
|
||||
// Middle lines: insert as new lines
|
||||
for i := 1; i < len(pastedLines); i++ {
|
||||
buf.InsertLine(y+i, pastedLines[i])
|
||||
}
|
||||
|
||||
// Last line: append the suffix
|
||||
lastLineIdx := y + len(pastedLines) - 1
|
||||
buf.SetLine(lastLineIdx, buf.Lines[lastLineIdx]+suffix)
|
||||
|
||||
// Set cursor to end of last pasted content (before suffix)
|
||||
win.SetCursorLine(lastLineIdx)
|
||||
win.SetCursorCol(len(buf.Lines[lastLineIdx]) - len(suffix) - 1)
|
||||
}
|
||||
}
|
||||
default:
|
||||
out := core.CommandOutput{
|
||||
@ -142,29 +169,56 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
{
|
||||
lines := reg.Content
|
||||
|
||||
// Shouldn't happen, just a check
|
||||
if len(lines) != 1 {
|
||||
out := core.CommandOutput{
|
||||
Lines: []string{"Charwise register should only have a single line of content."},
|
||||
Inline: true,
|
||||
IsError: true,
|
||||
}
|
||||
m.SetCommandOutput(&out)
|
||||
if len(lines) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
x := win.Cursor.Col
|
||||
y := win.Cursor.Line
|
||||
|
||||
cnt := strings.Repeat(lines[0], max(1, a.Count))
|
||||
curLine := buf.Lines[y]
|
||||
|
||||
// Catch edge cases, end of line, start of blank line
|
||||
insertAt := min(x, len(curLine))
|
||||
|
||||
if len(lines) == 1 {
|
||||
// Single-line charwise paste before cursor
|
||||
cnt := strings.Repeat(lines[0], max(1, a.Count))
|
||||
newLine := curLine[:insertAt] + cnt + curLine[insertAt:]
|
||||
buf.SetLine(y, newLine)
|
||||
|
||||
win.SetCursorCol(x + len(cnt))
|
||||
} else {
|
||||
// Multi-line charwise paste before cursor
|
||||
suffix := curLine[insertAt:] // Save the part after cursor
|
||||
|
||||
// For count > 1, we paste the content multiple times
|
||||
// Each paste continues from where the previous one ended
|
||||
var content strings.Builder
|
||||
for i := 0; i < a.Count; i++ {
|
||||
for j, line := range lines {
|
||||
if j > 0 {
|
||||
content.WriteString("\n")
|
||||
}
|
||||
content.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
// Split the pasted content into lines
|
||||
pastedLines := strings.Split(content.String(), "\n")
|
||||
|
||||
// First line: insert at cursor position
|
||||
buf.SetLine(y, curLine[:insertAt]+pastedLines[0])
|
||||
|
||||
// Middle lines: insert as new lines
|
||||
for i := 1; i < len(pastedLines); i++ {
|
||||
buf.InsertLine(y+i, pastedLines[i])
|
||||
}
|
||||
|
||||
// Last line: append the suffix
|
||||
lastLineIdx := y + len(pastedLines) - 1
|
||||
buf.SetLine(lastLineIdx, buf.Lines[lastLineIdx]+suffix)
|
||||
|
||||
// Set cursor to end of last pasted content (before suffix)
|
||||
win.SetCursorLine(lastLineIdx)
|
||||
win.SetCursorCol(len(buf.Lines[lastLineIdx]) - len(suffix) - 1)
|
||||
}
|
||||
}
|
||||
default:
|
||||
out := core.CommandOutput{
|
||||
|
||||
@ -821,37 +821,196 @@ func TestPasteBeforeCharwiseWithCount(t *testing.T) {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Multi-line Charwise Paste Tests (from visual mode yank)
|
||||
// Multi-line Charwise Paste Tests (from visual mode yank like vi{)
|
||||
// =============================================================================
|
||||
|
||||
func TestPasteCharwiseMultiLine(t *testing.T) {
|
||||
t.Run("p with multi-line charwise content errors gracefully", func(t *testing.T) {
|
||||
t.Run("p with 2-line charwise content inserts correctly", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"line1", "line2"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 1}), // on 'e'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"AAA", "BBB"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Current implementation errors - line should be unchanged
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello' (unchanged due to error)", m.ActiveBuffer().Lines[0])
|
||||
// Should paste after 'e': "heAAA\nBBBllo"
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "heAAA" {
|
||||
t.Errorf("Line(0) = %q, want 'heAAA'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "BBBllo" {
|
||||
t.Errorf("Line(1) = %q, want 'BBBllo'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P with multi-line charwise content errors gracefully", func(t *testing.T) {
|
||||
t.Run("p with 3-line charwise content inserts correctly", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"test"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 1}), // on 'e'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"AAA", "BBB", "CCC"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should paste: "teAAA\nBBB\nCCCst"
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "teAAA" {
|
||||
t.Errorf("Line(0) = %q, want 'teAAA'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "BBB" {
|
||||
t.Errorf("Line(1) = %q, want 'BBB'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2] != "CCCst" {
|
||||
t.Errorf("Line(2) = %q, want 'CCCst'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p with multi-line at start of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}), // on 'h'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X", "Y"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After 'h': "hX\nYello"
|
||||
if m.ActiveBuffer().Lines[0] != "hX" {
|
||||
t.Errorf("Line(0) = %q, want 'hX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "Yello" {
|
||||
t.Errorf("Line(1) = %q, want 'Yello'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p with multi-line at end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X", "Y"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After 'o': "helloX\nY"
|
||||
if m.ActiveBuffer().Lines[0] != "helloX" {
|
||||
t.Errorf("Line(0) = %q, want 'helloX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "Y" {
|
||||
t.Errorf("Line(1) = %q, want 'Y'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p with multi-line on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{""}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"line1", "line2"}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"AAA", "BBB"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.ActiveBuffer().Lines[0] != "AAA" {
|
||||
t.Errorf("Line(0) = %q, want 'AAA'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "BBB" {
|
||||
t.Errorf("Line(1) = %q, want 'BBB'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P with multi-line charwise content before cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}), // on first 'l'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X", "Y"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Current implementation errors - line should be unchanged
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
t.Errorf("Line(0) = %q, want 'hello' (unchanged due to error)", m.ActiveBuffer().Lines[0])
|
||||
// Before 'l': "heX\nYllo"
|
||||
if m.ActiveBuffer().Lines[0] != "heX" {
|
||||
t.Errorf("Line(0) = %q, want 'heX'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "Yllo" {
|
||||
t.Errorf("Line(1) = %q, want 'Yllo'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("P with multi-line at start of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X", "Y"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Before 'h': "X\nYhello"
|
||||
if m.ActiveBuffer().Lines[0] != "X" {
|
||||
t.Errorf("Line(0) = %q, want 'X'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "Yhello" {
|
||||
t.Errorf("Line(1) = %q, want 'Yhello'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("p with multi-line and count", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"test"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 1}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"A", "B"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// 2p should paste twice: "teA\nBA\nBst"
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "teA" {
|
||||
t.Errorf("Line(0) = %q, want 'teA'", m.ActiveBuffer().Lines[0])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[1] != "BA" {
|
||||
t.Errorf("Line(1) = %q, want 'BA'", m.ActiveBuffer().Lines[1])
|
||||
}
|
||||
if m.ActiveBuffer().Lines[2] != "Bst" {
|
||||
t.Errorf("Line(2) = %q, want 'Bst'", m.ActiveBuffer().Lines[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("real world: vi{ then y then p", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{
|
||||
"function() {",
|
||||
" body",
|
||||
"}",
|
||||
"test",
|
||||
}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
// Yank the content inside braces
|
||||
sendKeys(tm, "v", "i", "{", "y")
|
||||
// Move to test line and paste
|
||||
sendKeys(tm, "j", "j", "$", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// The yanked content should be multi-line charwise
|
||||
reg, ok := m.GetRegister('"')
|
||||
if !ok {
|
||||
t.Fatal("register not found")
|
||||
}
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
// Should paste after 't' in "test"
|
||||
// Depending on what vi{ yanks, this verifies multi-line paste works
|
||||
if m.ActiveBuffer().LineCount() < 4 {
|
||||
t.Errorf("LineCount() = %d, want at least 4", m.ActiveBuffer().LineCount())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user