diff --git a/_examples/demo.go b/_examples/demo.go index 0ba3c3b..d956da4 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -46,32 +46,6 @@ func cursorUp(g *gocui.Gui, v *gocui.View) error { return nil } -func cursorLeft(g *gocui.Gui, v *gocui.View) error { - if v != nil { - ox, oy := v.Origin() - cx, cy := v.Cursor() - if err := v.SetCursor(cx-1, cy); err != nil && ox > 0 { - if err := v.SetOrigin(ox-1, oy); err != nil { - return err - } - } - } - return nil -} - -func cursorRight(g *gocui.Gui, v *gocui.View) error { - if v != nil { - cx, cy := v.Cursor() - if err := v.SetCursor(cx+1, cy); err != nil { - ox, oy := v.Origin() - if err := v.SetOrigin(ox+1, oy); err != nil { - return err - } - } - } - return nil -} - func getLine(g *gocui.Gui, v *gocui.View) error { var l string var err error @@ -115,16 +89,10 @@ func keybindings(g *gocui.Gui) error { if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { return err } - if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil { + if err := g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil { return err } - if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil { - return err - } - if err := g.SetKeybinding("", gocui.KeyArrowLeft, gocui.ModNone, cursorLeft); err != nil { - return err - } - if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, cursorRight); err != nil { + if err := g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil { return err } if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { diff --git a/edit.go b/edit.go index 1ac4cd2..e56a5e6 100644 --- a/edit.go +++ b/edit.go @@ -6,65 +6,137 @@ package gocui import "github.com/nsf/termbox-go" -// handleEdit manages the edition mode. -func (g *Gui) handleEdit(v *View, ev *termbox.Event) error { +// handleEdit manages the edition mode. We do not handle errors here because if +// an error happens, it is enough to keep the view without modifications. +func (g *Gui) handleEdit(v *View, ev *termbox.Event) { switch { case ev.Ch != 0 && ev.Mod == 0: - return v.editWrite(ev.Ch) + v.editWrite(ev.Ch) case ev.Key == termbox.KeySpace: - return v.editWrite(' ') + v.editWrite(' ') case ev.Key == termbox.KeyBackspace || ev.Key == termbox.KeyBackspace2: - return v.editDelete(true) + v.editDelete(true) case ev.Key == termbox.KeyDelete: - return v.editDelete(false) + v.editDelete(false) case ev.Key == termbox.KeyInsert: v.overwrite = !v.overwrite case ev.Key == termbox.KeyEnter: - return v.editLine() + v.editNewLine() + case ev.Key == termbox.KeyArrowDown: + v.moveCursor(0, 1, false) + case ev.Key == termbox.KeyArrowUp: + v.moveCursor(0, -1, false) + case ev.Key == termbox.KeyArrowLeft: + v.moveCursor(-1, 0, false) + case ev.Key == termbox.KeyArrowRight: + v.moveCursor(1, 0, false) } - return nil } // editWrite writes a rune at the cursor position. -func (v *View) editWrite(ch rune) error { +func (v *View) editWrite(ch rune) { v.writeRune(v.cx, v.cy, ch) - if err := v.SetCursor(v.cx+1, v.cy); err != nil { - if err := v.SetOrigin(v.ox+1, v.oy); err != nil { - return err - } - } - return nil + v.moveCursor(1, 0, true) } // editDelete deletes a rune at the cursor position. back determines // the direction. -func (v *View) editDelete(back bool) error { +func (v *View) editDelete(back bool) { if back { - v.deleteRune(v.cx-1, v.cy) - if err := v.SetCursor(v.cx-1, v.cy); err != nil && v.ox > 0 { - if err := v.SetOrigin(v.ox-1, v.oy); err != nil { - return err - } + if v.cx == 0 { + v.mergeLines(v.cy - 1) + } else { + v.deleteRune(v.cx-1, v.cy) } + v.moveCursor(-1, 0, true) } else { - v.deleteRune(v.cx, v.cy) + y := v.oy + v.cy + if y >= 0 && y < len(v.viewLines) && v.cx == len(v.viewLines[y].line) { + v.mergeLines(v.cy) + } else { + v.deleteRune(v.cx, v.cy) + } } - return nil } -// editLine inserts a new line under the cursor. -func (v *View) editLine() error { +// editNewLine inserts a new line under the cursor. +func (v *View) editNewLine() { v.breakLine(v.cx, v.cy) - if err := v.SetCursor(v.cx, v.cy+1); err != nil { - if err := v.SetOrigin(v.ox, v.oy+1); err != nil { - return err + + y := v.oy + v.cy + if y >= len(v.viewLines) || (y >= 0 && y < len(v.viewLines) && + !(v.Wrap && v.cx == 0 && v.viewLines[y].linesX > 0)) { + // new line at the end of the buffer or + // cursor is not at the beginning of a wrapped line + v.ox = 0 + v.cx = 0 + v.moveCursor(0, 1, true) + } +} + +// moveCursor moves the cursor taking into account the line or view widths and +// moves the origin when necessary. If writeMode is false, the cursor jumps to +// the next line when it reaches the end of the line, otherwise it jumps when +// the cursor reaches the width of the view. +func (v *View) moveCursor(dx, dy int, writeMode bool) { + maxX, maxY := v.Size() + cx, cy := v.cx+dx, v.cy+dy + y := v.oy + cy + + var curLineWidth, prevLineWidth int + // get the width of the current line + if writeMode { + curLineWidth = maxX - 1 + } else { + if y >= 0 && y < len(v.viewLines) { + w := len(v.viewLines[y].line) + if w < maxX { + curLineWidth = w + } else { + curLineWidth = maxX - 1 + } + } else { + curLineWidth = 0 } } - if err := v.SetCursor(0, v.cy); err != nil { - return err + // get the width of the previous line + if y-1 >= 0 && y-1 < len(v.viewLines) { + prevLineWidth = len(v.viewLines[y-1].line) + } else { + prevLineWidth = 0 } - if err := v.SetOrigin(0, v.oy); err != nil { - return err + + // adjust cursor's x position and view's x origin + if cx > curLineWidth { // move to next line + if dx > 0 { // horizontal movement + v.cx = 0 + cy += 1 + } else { // vertical movement + if curLineWidth > 0 { + v.cx = curLineWidth + } else { + v.cx = 0 + } + } + } else if cx < 0 { // move to previous line + if prevLineWidth > 0 { + v.cx = prevLineWidth + } else { + v.cx = 0 + } + cy -= 1 + } else { // stay on the same line + v.cx = cx + } + + // adjust cursor's y position and view's y origin + if cy >= maxY { + v.oy += 1 + } else if cy < 0 { + if v.oy > 0 { + v.oy -= 1 + } + } else { + v.cy = cy } - return nil } diff --git a/gui.go b/gui.go index fcb6314..9cefd06 100644 --- a/gui.go +++ b/gui.go @@ -455,15 +455,12 @@ func horizontalRune(ch rune) bool { // currentView's internal buffer is modified if currentView.Editable is true. func (g *Gui) onKey(ev *termbox.Event) error { if g.currentView != nil && g.currentView.Editable { - if err := g.handleEdit(g.currentView, ev); err != nil { - return err - } + g.handleEdit(g.currentView, ev) } var cv string if g.currentView != nil { cv = g.currentView.name - } for _, kb := range g.keybindings { if kb.h == nil { diff --git a/view.go b/view.go index f5c73d4..edf7763 100644 --- a/view.go +++ b/view.go @@ -328,27 +328,18 @@ func (v *View) writeRune(x, y int, ch rune) error { } if y >= len(v.lines) { - if y >= cap(v.lines) { - s := make([][]rune, y+1, (y+1)*2) - copy(s, v.lines) - v.lines = s - } else { - v.lines = v.lines[:y+1] - } + s := make([][]rune, y-len(v.lines)+1) + v.lines = append(v.lines, s...) } - if v.lines[y] == nil { - v.lines[y] = make([]rune, x+1, (x+1)*2) - } else if x >= len(v.lines[y]) { - if x >= cap(v.lines[y]) { - s := make([]rune, x+1, (x+1)*2) - copy(s, v.lines[y]) - v.lines[y] = s - } else { - v.lines[y] = v.lines[y][:x+1] - } + + olen := len(v.lines[y]) + if x >= len(v.lines[y]) { + s := make([]rune, x-len(v.lines[y])+1) + v.lines[y] = append(v.lines[y], s...) } - if !v.overwrite { - v.lines[y] = append(v.lines[y], ' ') + + if !v.overwrite && x < olen { + v.lines[y] = append(v.lines[y], '\x00') copy(v.lines[y][x+1:], v.lines[y][x:]) } v.lines[y][x] = ch @@ -365,13 +356,33 @@ func (v *View) deleteRune(x, y int) error { return err } - if x < 0 || y < 0 || y >= len(v.lines) || v.lines[y] == nil || x >= len(v.lines[y]) { + if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { return errors.New("invalid point") } v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...) return nil } +// mergeLines merges the lines "y" and "y+1" if possible. +func (v *View) mergeLines(y int) error { + v.tainted = true + + _, y, err := v.realPosition(0, y) + if err != nil { + return err + } + + if y < 0 || y >= len(v.lines) { + return errors.New("invalid point") + } + + if y < len(v.lines)-1 { // otherwise we don't need to merge anything + v.lines[y] = append(v.lines[y], v.lines[y+1]...) + v.lines = append(v.lines[:y+1], v.lines[y+2:]...) + } + return nil +} + // breakLine breaks a line of the internal buffer at the position corresponding // to the point (x, y). func (v *View) breakLine(x, y int) error { @@ -437,7 +448,7 @@ func (v *View) Word(x, y int) (string, error) { return "", err } - if y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { + if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { return "", errors.New("invalid point") } l := string(v.lines[y])