diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index 6bae6b50..a9ea8e87 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -2,8 +2,10 @@ package action import ( "os" + "time" "unicode/utf8" + "github.com/zyedidia/clipboard" "github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/screen" "github.com/zyedidia/micro/cmd/micro/util" @@ -18,11 +20,21 @@ func (h *BufHandler) MousePress(e *tcell.EventMouse) bool { // ScrollUpAction scrolls the view up func (h *BufHandler) ScrollUpAction() bool { + b := h.Buf + sspeed := b.Settings["scrollspeed"].(int) + if h.Win.StartLine >= sspeed { + h.Win.StartLine -= sspeed + } return false } // ScrollDownAction scrolls the view up func (h *BufHandler) ScrollDownAction() bool { + b := h.Buf + sspeed := b.Settings["scrollspeed"].(int) + if h.Win.StartLine <= h.Buf.LinesNum()-1-sspeed { + h.Win.StartLine += sspeed + } return false } @@ -358,12 +370,12 @@ func (h *BufHandler) IndentSelection() bool { tabsize := int(h.Buf.Settings["tabsize"].(float64)) indentsize := len(h.Buf.IndentString(tabsize)) for y := startY; y <= endY; y++ { - h.Buf.Insert(buffer.Loc{0, y}, h.Buf.IndentString(tabsize)) + h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize)) if y == startY && start.X > 0 { h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf)) } if y == endY { - h.Cursor.SetSelectionEnd(buffer.Loc{endX + indentsize + 1, endY}) + h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY}) } } h.Cursor.Relocate() @@ -371,21 +383,58 @@ func (h *BufHandler) IndentSelection() bool { return true } return false - return false } // OutdentLine moves the current line back one indentation func (h *BufHandler) OutdentLine() bool { + if h.Cursor.HasSelection() { + return false + } + + for x := 0; x < len(h.Buf.IndentString(h.Buf.Settings["tabsize"].(int))); x++ { + if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 { + break + } + h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y}) + } + h.Cursor.Relocate() return true } // OutdentSelection takes the current selection and moves it back one indent level func (h *BufHandler) OutdentSelection() bool { - return true + if h.Cursor.HasSelection() { + start := h.Cursor.CurSelection[0] + end := h.Cursor.CurSelection[1] + if end.Y < start.Y { + start, end = end, start + h.Cursor.SetSelectionStart(start) + h.Cursor.SetSelectionEnd(end) + } + + startY := start.Y + endY := end.Move(-1, h.Buf).Y + for y := startY; y <= endY; y++ { + for x := 0; x < len(h.Buf.IndentString(h.Buf.Settings["tabsize"].(int))); x++ { + if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 { + break + } + h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y}) + } + } + h.Cursor.Relocate() + + return true + } + return false } // InsertTab inserts a tab or spaces func (h *BufHandler) InsertTab() bool { + indent := h.Buf.IndentString(h.Buf.Settings["tabsize"].(int)) + tabBytes := len(indent) + bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes) + h.Buf.Insert(h.Cursor.Loc, indent[:bytesUntilIndent]) return true } @@ -422,36 +471,91 @@ func (h *BufHandler) FindPrevious() bool { // Undo undoes the last action func (h *BufHandler) Undo() bool { + // TODO: clear cursors and message + h.Buf.Undo() return true } // Redo redoes the last action func (h *BufHandler) Redo() bool { + // TODO: clear cursors and message + h.Buf.Redo() return true } // Copy the selection to the system clipboard func (h *BufHandler) Copy() bool { + if h.Cursor.HasSelection() { + h.Cursor.CopySelection("clipboard") + h.freshClip = true + // TODO: message + } return true } // CutLine cuts the current line to the clipboard func (h *BufHandler) CutLine() bool { + h.Cursor.SelectLine() + if !h.Cursor.HasSelection() { + return false + } + if h.freshClip == true { + if h.Cursor.HasSelection() { + if clip, err := clipboard.ReadAll("clipboard"); err != nil { + // messenger.Error(err) + } else { + clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard") + } + } + } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false { + h.Copy() + } + h.freshClip = true + h.lastCutTime = time.Now() + h.Cursor.DeleteSelection() + h.Cursor.ResetSelection() + // messenger.Message("Cut line") return true } // Cut the selection to the system clipboard func (h *BufHandler) Cut() bool { - return true + if h.Cursor.HasSelection() { + h.Cursor.CopySelection("clipboard") + h.Cursor.DeleteSelection() + h.Cursor.ResetSelection() + h.freshClip = true + // messenger.Message("Cut selection") + + return true + } else { + return h.CutLine() + } } // DuplicateLine duplicates the current line or selection func (h *BufHandler) DuplicateLine() bool { + if h.Cursor.HasSelection() { + h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection())) + } else { + h.Cursor.End() + h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y))) + // h.Cursor.Right() + } + + // messenger.Message("Duplicated line") return true } // DeleteLine deletes the current line func (h *BufHandler) DeleteLine() bool { + h.Cursor.SelectLine() + if !h.Cursor.HasSelection() { + return false + } + h.Cursor.DeleteSelection() + h.Cursor.ResetSelection() + // messenger.Message("Deleted line") return true } @@ -484,6 +588,11 @@ func (h *BufHandler) JumpToMatchingBrace() bool { // SelectAll selects the entire buffer func (h *BufHandler) SelectAll() bool { + h.Cursor.SetSelectionStart(h.Buf.Start()) + h.Cursor.SetSelectionEnd(h.Buf.End()) + // Put the cursor at the beginning + h.Cursor.X = 0 + h.Cursor.Y = 0 return true } @@ -494,11 +603,18 @@ func (h *BufHandler) OpenFile() bool { // Start moves the viewport to the start of the buffer func (h *BufHandler) Start() bool { + h.Win.StartLine = 0 return false } // End moves the viewport to the end of the buffer func (h *BufHandler) End() bool { + // TODO: softwrap problems? + if h.Win.Height > h.Buf.LinesNum() { + h.Win.StartLine = 0 + } else { + h.StartLine = h.Buf.LinesNum() - h.Win.Height + } return false } @@ -544,6 +660,13 @@ func (h *BufHandler) HalfPageDown() bool { // ToggleRuler turns line numbers off and on func (h *BufHandler) ToggleRuler() bool { + if !h.Buf.Settings["ruler"].(bool) { + h.Buf.Settings["ruler"] = true + // messenger.Message("Enabled ruler") + } else { + h.Buf.Settings["ruler"] = false + // messenger.Message("Disabled ruler") + } return false } diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 0cb4ebb0..80d490fa 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -4,6 +4,7 @@ import ( "time" "github.com/zyedidia/micro/cmd/micro/buffer" + "github.com/zyedidia/micro/cmd/micro/display" "github.com/zyedidia/micro/cmd/micro/util" "github.com/zyedidia/tcell" ) @@ -46,6 +47,7 @@ func BufMapMouse(k MouseEvent, action string) { // visual positions for mouse clicks and scrolling type BufHandler struct { Buf *buffer.Buffer + Win *display.BufWindow cursors []*buffer.Cursor Cursor *buffer.Cursor // the active cursor @@ -81,9 +83,10 @@ type BufHandler struct { tripleClick bool } -func NewBufHandler(buf *buffer.Buffer) *BufHandler { +func NewBufHandler(buf *buffer.Buffer, win *display.BufWindow) *BufHandler { h := new(BufHandler) h.Buf = buf + h.Win = win h.cursors = []*buffer.Cursor{buffer.NewCursor(buf, buf.StartCursor)} h.Cursor = h.cursors[0] @@ -117,7 +120,9 @@ func (h *BufHandler) HandleEvent(event tcell.Event) { func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { if action, ok := BufKeyBindings[e]; ok { - action(h) + if action(h) { + h.Win.Relocate() + } return true } return false @@ -125,7 +130,9 @@ func (h *BufHandler) DoKeyEvent(e KeyEvent) bool { func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { if action, ok := BufMouseBindings[e]; ok { - action(h, te) + if action(h, te) { + h.Win.Relocate() + } return true } return false diff --git a/cmd/micro/display/statusline.go b/cmd/micro/display/statusline.go index c98950d1..b9343fa9 100644 --- a/cmd/micro/display/statusline.go +++ b/cmd/micro/display/statusline.go @@ -8,7 +8,6 @@ import ( "strconv" "unicode/utf8" - "github.com/zyedidia/micro/cmd/micro/action" "github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/config" "github.com/zyedidia/micro/cmd/micro/screen" @@ -84,12 +83,13 @@ func (s *StatusLine) Display() { option := name[4:] return []byte(fmt.Sprint(s.FindOpt(string(option)))) } else if bytes.HasPrefix(name, []byte("bind")) { - binding := string(name[5:]) - for k, v := range action.Bindings { - if v == binding { - return []byte(k) - } - } + // binding := string(name[5:]) + // TODO: search bindings + // for k, v := range action.Bindings { + // if v == binding { + // return []byte(k) + // } + // } return []byte("null") } else { return []byte(s.Info[string(name)](s.win.Buf)) diff --git a/cmd/micro/display/window.go b/cmd/micro/display/window.go index 14630a5d..d4967b5e 100644 --- a/cmd/micro/display/window.go +++ b/cmd/micro/display/window.go @@ -1,6 +1,7 @@ package display import ( + "log" "strconv" "unicode/utf8" @@ -37,12 +38,15 @@ type BufWindow struct { Buf *buffer.Buffer sline *StatusLine + + lineHeight []int } // NewBufWindow creates a new window at a location in the screen with a width and height func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow { w := new(BufWindow) w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf + w.lineHeight = make([]int, height) w.sline = NewStatusLine(w) @@ -58,6 +62,71 @@ func (w *BufWindow) Clear() { } } +// Bottomline returns the line number of the lowest line in the view +// You might think that this is obviously just v.StartLine + v.Height +// but if softwrap is enabled things get complicated since one buffer +// line can take up multiple lines in the view +func (w *BufWindow) Bottomline() int { + // b := w.Buf + + // if !b.Settings["softwrap"].(bool) { + // return w.StartLine + w.Height + // } + + prev := 0 + for i, l := range w.lineHeight { + if l >= prev { + log.Println("lineHeight[", i, "] = ", l) + prev = l + } else { + break + } + } + return prev +} + +// Relocate moves the view window so that the cursor is in view +// This is useful if the user has scrolled far away, and then starts typing +// Returns true if the window location is moved +func (w *BufWindow) Relocate() bool { + b := w.Buf + height := w.Bottomline() + 1 - w.StartLine + log.Println("Height: ", height) + ret := false + activeC := w.Buf.GetActiveCursor() + cy := activeC.Y + scrollmargin := int(b.Settings["scrollmargin"].(float64)) + if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 { + w.StartLine = cy - scrollmargin + ret = true + } else if cy < w.StartLine { + w.StartLine = cy + ret = true + } + if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin { + w.StartLine = cy - height + 1 + scrollmargin + ret = true + } else if cy >= b.LinesNum()-scrollmargin && cy >= height { + w.StartLine = b.LinesNum() - height + log.Println(w.StartLine) + ret = true + } + + // TODO: horizontal scroll + // if !b.Settings["softwrap"].(bool) { + // cx := activeC.GetVisualX() + // if cx < w.StartCol { + // w.StartCol = cx + // ret = true + // } + // if cx+v.lineNumOffset+1 > v.leftCol+v.Width { + // v.leftCol = cx - v.Width + v.lineNumOffset + 1 + // ret = true + // } + // } + return ret +} + func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) { lineNum := strconv.Itoa(bloc.Y + 1) @@ -173,6 +242,8 @@ func (w *BufWindow) displayBuffer() { nColsBeforeStart-- } + w.lineHeight[vloc.Y] = bloc.Y + totalwidth := bloc.X - nColsBeforeStart for len(line) > 0 { if activeC.X == bloc.X && activeC.Y == bloc.Y { @@ -217,6 +288,7 @@ func (w *BufWindow) displayBuffer() { break } vloc.X = 0 + w.lineHeight[vloc.Y] = bloc.Y // This will draw an empty line number because the current line is wrapped w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc) } diff --git a/cmd/micro/editpane.go b/cmd/micro/editpane.go index 90739fba..a551a53e 100644 --- a/cmd/micro/editpane.go +++ b/cmd/micro/editpane.go @@ -13,8 +13,10 @@ type EditPane struct { func NewBufEditPane(x, y, width, height int, b *buffer.Buffer) *EditPane { e := new(EditPane) - e.Window = display.NewBufWindow(x, y, width, height, b) - e.Handler = action.NewBufHandler(b) + // TODO: can probably replace editpane with bufhandler entirely + w := display.NewBufWindow(x, y, width, height, b) + e.Window = w + e.Handler = action.NewBufHandler(b, w) return e } diff --git a/cmd/micro/util/util.go b/cmd/micro/util/util.go index 874f3b48..6d324434 100644 --- a/cmd/micro/util/util.go +++ b/cmd/micro/util/util.go @@ -231,3 +231,19 @@ func EscapePath(path string) string { path = filepath.ToSlash(path) return strings.Replace(path, "/", "%", -1) } + +// GetLeadingWhitespace returns the leading whitespace of the given byte array +func GetLeadingWhitespace(b []byte) []byte { + ws := []byte{} + for len(b) > 0 { + r, size := utf8.DecodeRune(b) + if r == ' ' || r == '\t' { + ws = append(ws, byte(r)) + } else { + break + } + + b = b[size:] + } + return ws +}