Skip to content

Commit c39eb95

Browse files
committed
Merge branch 'copy-cut-paste'
2 parents 7f186e0 + 8138291 commit c39eb95

File tree

7 files changed

+273
-1
lines changed

7 files changed

+273
-1
lines changed

editor/editor.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"sync"
77

8+
"github.com/itchyny/bed/buffer"
89
"github.com/itchyny/bed/event"
910
"github.com/itchyny/bed/mode"
1011
"github.com/itchyny/bed/state"
@@ -20,6 +21,7 @@ type Editor struct {
2021
searchTarget string
2122
searchMode rune
2223
prevEventType event.Type
24+
buffer *buffer.Buffer
2325
err error
2426
errtyp int
2527
eventCh chan event.Event
@@ -103,6 +105,17 @@ func (e *Editor) emit(ev event.Event) (redraw bool, finish bool) {
103105
width, height := e.ui.Size()
104106
e.wm.Resize(width, height-1)
105107
redraw = true
108+
case event.Copied:
109+
e.buffer, e.mode, e.prevMode = ev.Buffer, mode.Normal, e.mode
110+
if l, err := e.buffer.Len(); err != nil {
111+
e.err, e.errtyp = err, state.MessageError
112+
} else {
113+
e.err, e.errtyp = fmt.Errorf("%d (0x%x) bytes %s", l, l, ev.Arg), state.MessageInfo
114+
}
115+
redraw = true
116+
case event.Pasted:
117+
e.err, e.errtyp = fmt.Errorf("%d (0x%x) bytes pasted", ev.Count, ev.Count), state.MessageInfo
118+
redraw = true
106119
default:
107120
switch ev.Type {
108121
case event.StartInsert, event.StartInsertHead, event.StartAppend, event.StartAppendEnd:
@@ -141,6 +154,12 @@ func (e *Editor) emit(ev event.Event) (redraw bool, finish bool) {
141154
ev.Arg, ev.Rune = e.searchTarget, e.searchMode
142155
case event.PreviousSearch:
143156
ev.Arg, ev.Rune = e.searchTarget, e.searchMode
157+
case event.Paste, event.PastePrev:
158+
if e.buffer == nil {
159+
e.mu.Unlock()
160+
return
161+
}
162+
ev.Buffer = e.buffer
144163
}
145164
if e.mode == mode.Cmdline || e.mode == mode.Search ||
146165
ev.Type == event.ExitCmdline || ev.Type == event.ExecuteCmdline {

editor/editor_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,57 @@ func TestEditorCmdlineQuit(t *testing.T) {
310310
t.Errorf("err should be nil but got: %v", err)
311311
}
312312
}
313+
314+
func TestEditorCopyCutPaste(t *testing.T) {
315+
f1, _ := ioutil.TempFile("", "bed-test-editor-copy-cut-paste1")
316+
f2, _ := ioutil.TempFile("", "bed-test-editor-copy-cut-paste2")
317+
defer os.Remove(f1.Name())
318+
defer os.Remove(f2.Name())
319+
str := "Hello, world!"
320+
_, _ = f1.WriteString(str)
321+
_ = f1.Close()
322+
_ = f2.Close()
323+
ui := newTestUI()
324+
editor := NewEditor(ui, window.NewManager(), cmdline.NewCmdline())
325+
if err := editor.Init(); err != nil {
326+
t.Errorf("err should be nil but got: %v", err)
327+
}
328+
if err := editor.Open(f1.Name()); err != nil {
329+
t.Errorf("err should be nil but got: %v", err)
330+
}
331+
go func() {
332+
for _, e := range []struct {
333+
typ event.Type
334+
ch rune
335+
count int64
336+
arg string
337+
}{
338+
{event.CursorNext, 'w', 2, ""}, {event.StartVisual, 'v', 0, ""},
339+
{event.CursorNext, 'w', 5, ""}, {event.Copy, 'y', 0, ""},
340+
{event.CursorNext, 'w', 3, ""}, {event.Paste, 'p', 0, ""},
341+
{event.CursorPrev, 'b', 2, ""}, {event.StartVisual, 'v', 0, ""},
342+
{event.CursorPrev, 'b', 5, ""}, {event.Cut, 'd', 0, ""},
343+
{event.CursorNext, 'w', 5, ""}, {event.PastePrev, 'P', 0, ""},
344+
{event.Write, 'w', 0, f2.Name()},
345+
} {
346+
time.Sleep(50 * time.Millisecond)
347+
ui.Emit(event.Event{Type: e.typ, Rune: e.ch, Count: e.count, Arg: e.arg})
348+
}
349+
time.Sleep(500 * time.Millisecond)
350+
ui.Emit(event.Event{Type: event.Quit})
351+
}()
352+
if err := editor.Run(); err != nil {
353+
t.Errorf("err should be nil but got: %v", err)
354+
}
355+
if err := editor.err; !strings.HasSuffix(err.Error(), "19 (0x13) bytes written") {
356+
t.Errorf("err should be ends with %q but got: %v", "19 (0x13) bytes written", err)
357+
}
358+
if err := editor.Close(); err != nil {
359+
t.Errorf("err should be nil but got: %v", err)
360+
}
361+
bs, _ := ioutil.ReadFile(f2.Name())
362+
expected := "Hell w woo,llo,rld!"
363+
if string(bs) != expected {
364+
t.Errorf("file contents should be %q but got %q", expected, string(bs))
365+
}
366+
}

editor/key.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ func defaultKeyManagers() map[mode.Mode]*key.Manager {
5252
km.Register(event.Decrement, "c-x")
5353
km.Register(event.Decrement, "-")
5454

55+
km.Register(event.Paste, "p")
56+
km.Register(event.PastePrev, "P")
57+
5558
km.Register(event.StartInsert, "i")
5659
km.Register(event.StartInsertHead, "I")
5760
km.Register(event.StartAppend, "a")
@@ -157,6 +160,8 @@ func defaultKeyManagers() map[mode.Mode]*key.Manager {
157160
km.Register(event.PageEnd, "G")
158161
km.Register(event.SwitchFocus, "tab")
159162
km.Register(event.SwitchFocus, "backtab")
163+
km.Register(event.Copy, "y")
164+
km.Register(event.Cut, "d")
160165
kms[mode.Visual] = km
161166

162167
km = key.NewManager(false)

event/event.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package event
22

3-
import "github.com/itchyny/bed/mode"
3+
import (
4+
"github.com/itchyny/bed/buffer"
5+
"github.com/itchyny/bed/mode"
6+
)
47

58
// Event represents the event emitted by UI.
69
type Event struct {
@@ -12,6 +15,7 @@ type Event struct {
1215
Arg string
1316
Error error
1417
Mode mode.Mode
18+
Buffer *buffer.Buffer
1519
}
1620

1721
// Type ...
@@ -70,6 +74,13 @@ const (
7074
SwitchVisualEnd
7175
ExitVisual
7276

77+
Copy
78+
Cut
79+
Copied
80+
Paste
81+
PastePrev
82+
Pasted
83+
7384
StartCmdlineCommand
7485
StartCmdlineSearchForward
7586
StartCmdlineSearchBackward

window/manager.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,18 @@ func (m *Manager) Emit(e event.Event) {
218218
if err := m.quit(e); err != nil {
219219
m.eventCh <- event.Event{Type: event.Error, Error: err}
220220
}
221+
case event.Copy:
222+
m.mu.Lock()
223+
m.eventCh <- event.Event{Type: event.Copied, Buffer: m.windows[m.windowIndex].copy(), Arg: "yanked"}
224+
m.mu.Unlock()
225+
case event.Cut:
226+
m.mu.Lock()
227+
m.eventCh <- event.Event{Type: event.Copied, Buffer: m.windows[m.windowIndex].cut(), Arg: "deleted"}
228+
m.mu.Unlock()
229+
case event.Paste, event.PastePrev:
230+
m.mu.Lock()
231+
m.eventCh <- event.Event{Type: event.Pasted, Count: m.windows[m.windowIndex].paste(e)}
232+
m.mu.Unlock()
221233
case event.Write:
222234
if err := m.write(e); err != nil {
223235
m.eventCh <- event.Event{Type: event.Error, Error: err}

window/manager_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"testing"
1010

11+
"github.com/itchyny/bed/buffer"
1112
"github.com/itchyny/bed/event"
1213
"github.com/itchyny/bed/layout"
1314
"github.com/itchyny/bed/mode"
@@ -204,3 +205,109 @@ func TestManagerWincmd(t *testing.T) {
204205

205206
wm.Close()
206207
}
208+
209+
func TestManagerCopyCutPaste(t *testing.T) {
210+
wm := NewManager()
211+
eventCh, redrawCh, waitCh := make(chan event.Event), make(chan struct{}), make(chan struct{})
212+
wm.Init(eventCh, redrawCh)
213+
f, err := ioutil.TempFile("", "bed-test-manager-copy-cut-paste")
214+
str := "Hello, world!"
215+
_, err = f.WriteString(str)
216+
if err != nil {
217+
t.Errorf("err should be nil but got %v", err)
218+
}
219+
if err := f.Close(); err != nil {
220+
t.Errorf("err should be nil but got: %v", err)
221+
}
222+
defer os.Remove(f.Name())
223+
wm.SetSize(110, 20)
224+
if err := wm.Open(f.Name()); err != nil {
225+
t.Errorf("err should be nil but got: %v", err)
226+
}
227+
_, _, _, _ = wm.State()
228+
go func() {
229+
<-redrawCh
230+
<-redrawCh
231+
<-redrawCh
232+
waitCh <- struct{}{}
233+
ev := <-eventCh
234+
if ev.Type != event.Copied {
235+
t.Errorf("event type should be %d but got: %d", event.Copied, ev.Type)
236+
}
237+
if ev.Buffer == nil {
238+
t.Errorf("Buffer should not be nil but got: %#v", ev)
239+
}
240+
if ev.Arg != "yanked" {
241+
t.Errorf("Arg should be %q but got: %q", "yanked", ev.Arg)
242+
}
243+
p := make([]byte, 20)
244+
_, _ = ev.Buffer.ReadAt(p, 0)
245+
if !strings.HasPrefix(string(p), "lo, worl") {
246+
t.Errorf("buffer string should be %q but got: %q", "", string(p))
247+
}
248+
waitCh <- struct{}{}
249+
<-redrawCh
250+
<-redrawCh
251+
waitCh <- struct{}{}
252+
ev = <-eventCh
253+
if ev.Type != event.Copied {
254+
t.Errorf("event type should be %d but got: %d", event.Copied, ev.Type)
255+
}
256+
if ev.Buffer == nil {
257+
t.Errorf("Buffer should not be nil but got: %#v", ev)
258+
}
259+
if ev.Arg != "deleted" {
260+
t.Errorf("Arg should be %q but got: %q", "deleted", ev.Arg)
261+
}
262+
p = make([]byte, 20)
263+
_, _ = ev.Buffer.ReadAt(p, 0)
264+
if !strings.HasPrefix(string(p), "lo, wo") {
265+
t.Errorf("buffer string should be %q but got: %q", "", string(p))
266+
}
267+
windowStates, _, _, _ := wm.State()
268+
ws := windowStates[0]
269+
if ws.Length != int64(7) {
270+
t.Errorf("Length should be %d but got %d", int64(7), ws.Length)
271+
}
272+
expected := "Helrld!"
273+
if !strings.HasPrefix(string(ws.Bytes), expected) {
274+
t.Errorf("Bytes should start with %q but got %q", expected, string(ws.Bytes))
275+
}
276+
waitCh <- struct{}{}
277+
<-redrawCh
278+
waitCh <- struct{}{}
279+
ev = <-eventCh
280+
if ev.Type != event.Pasted {
281+
t.Errorf("event type should be %d but got: %d", event.Pasted, ev.Type)
282+
}
283+
if ev.Count != 18 {
284+
t.Errorf("Count should be %d but got: %d", 18, ev.Count)
285+
}
286+
windowStates, _, _, _ = wm.State()
287+
ws = windowStates[0]
288+
if ws.Length != int64(25) {
289+
t.Errorf("Length should be %d but got %d", int64(25), ws.Length)
290+
}
291+
expected = "Hefoobarfoobarfoobarlrld!"
292+
if !strings.HasPrefix(string(ws.Bytes), expected) {
293+
t.Errorf("Bytes should start with %q but got %q", expected, string(ws.Bytes))
294+
}
295+
close(waitCh)
296+
}()
297+
wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Normal, Count: 3})
298+
wm.Emit(event.Event{Type: event.StartVisual})
299+
wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Visual, Count: 7})
300+
<-waitCh
301+
wm.Emit(event.Event{Type: event.Copy})
302+
<-waitCh
303+
wm.Emit(event.Event{Type: event.StartVisual})
304+
wm.Emit(event.Event{Type: event.CursorNext, Mode: mode.Visual, Count: 5})
305+
<-waitCh
306+
wm.Emit(event.Event{Type: event.Cut})
307+
<-waitCh
308+
wm.Emit(event.Event{Type: event.CursorPrev, Mode: mode.Normal, Count: 2})
309+
<-waitCh
310+
wm.Emit(event.Event{Type: event.Paste, Buffer: buffer.NewBuffer(strings.NewReader("foobar")), Count: 3})
311+
<-waitCh
312+
wm.Close()
313+
}

window/window.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,70 @@ func (w *window) exitVisual() {
801801
w.visualStart = -1
802802
}
803803

804+
func (w *window) copy() *buffer.Buffer {
805+
w.mu.Lock()
806+
defer w.mu.Unlock()
807+
if w.visualStart < 0 {
808+
panic("window#copy should be called in visual mode")
809+
}
810+
start, end := w.visualStart, w.cursor
811+
if start > end {
812+
start, end = end, start
813+
}
814+
w.cursor = w.visualStart
815+
w.visualStart = -1
816+
if w.cursor < w.offset {
817+
w.offset = w.cursor / w.width * w.width
818+
} else if w.cursor >= w.offset+w.height*w.width {
819+
w.offset = (w.cursor - w.height*w.width + w.width) / w.width * w.width
820+
}
821+
return w.buffer.Copy(start, end+1)
822+
}
823+
824+
func (w *window) cut() *buffer.Buffer {
825+
w.mu.Lock()
826+
defer w.mu.Unlock()
827+
if w.visualStart < 0 {
828+
panic("window#cut should be called in visual mode")
829+
}
830+
start, end := w.visualStart, w.cursor
831+
if start > end {
832+
start, end = end, start
833+
}
834+
w.visualStart = -1
835+
b := w.buffer.Copy(start, end+1)
836+
w.buffer.Cut(start, end+1)
837+
w.length, _ = w.buffer.Len()
838+
w.cursor = mathutil.MinInt64(start, mathutil.MaxInt64(w.length, 1)-1)
839+
if w.cursor < w.offset {
840+
w.offset = w.cursor / w.width * w.width
841+
} else if w.cursor >= w.offset+w.height*w.width {
842+
w.offset = (w.cursor - w.height*w.width + w.width) / w.width * w.width
843+
}
844+
return b
845+
}
846+
847+
func (w *window) paste(e event.Event) int64 {
848+
w.mu.Lock()
849+
defer w.mu.Unlock()
850+
count := mathutil.MaxInt64(e.Count, 1)
851+
pos := w.cursor
852+
if e.Type != event.PastePrev {
853+
pos = mathutil.MinInt64(w.cursor+1, w.length)
854+
}
855+
for i := int64(0); i < count; i++ {
856+
w.buffer.Paste(pos, e.Buffer)
857+
}
858+
l, _ := e.Buffer.Len()
859+
w.length, _ = w.buffer.Len()
860+
w.cursor = pos + mathutil.MinInt64(l*count-1, mathutil.MaxInt64(w.length, 1)-1-pos)
861+
if w.cursor >= w.offset+w.height*w.width {
862+
w.offset = (w.cursor - w.height*w.width + w.width) / w.width * w.width
863+
}
864+
w.changedTick++
865+
return l * count
866+
}
867+
804868
func (w *window) search(str string, forward bool) {
805869
if forward {
806870
w.searchForward(str)

0 commit comments

Comments
 (0)