X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fview.go;h=8a009b6504903588095febb95be640c35d1b68ef;hb=21840d3ffeee2ef4f3e3888c9ee72caac298f56e;hp=722f42b044e13a40e4deda19270ff2d530dd24ff;hpb=04b4dbbfee119c7dd00cc6230e6d7f2155146576;p=micro.git diff --git a/cmd/micro/view.go b/cmd/micro/view.go index 722f42b0..8a009b65 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -11,23 +11,18 @@ import ( ) type ViewType struct { + kind int readonly bool // The file cannot be edited scratch bool // The file cannot be saved } var ( - vtDefault = ViewType{false, false} - vtHelp = ViewType{true, true} - vtLog = ViewType{true, true} - vtScratch = ViewType{false, true} + vtDefault = ViewType{0, false, false} + vtHelp = ViewType{1, true, true} + vtLog = ViewType{2, true, true} + vtScratch = ViewType{3, false, true} ) -type scrollInfo struct { - left int - offset int - style *tcell.Style -} - // The View struct stores information about a view into a buffer. // It stores information about the cursor, and the viewport // that the user sees the buffer from. @@ -38,8 +33,7 @@ type View struct { // The topmost line, used for vertical scrolling Topline int // The leftmost column, used for horizontal scrolling - leftCol int - hScrollInfo []*scrollInfo + leftCol int // Specifies whether or not this view holds a help buffer Type ViewType @@ -166,23 +160,6 @@ func (v *View) ToggleTabbar() { } } -func (v *View) calcHScrollInfo() { - v.hScrollInfo = make([]*scrollInfo, v.Height) - for i := v.Topline; i < v.Topline+v.Height; i++ { - if i >= v.Buf.NumLines { - break - } - - left, offset, style := visualToCharPos(v.leftCol, i, v.Buf.Line(i), v.Buf, int(v.Buf.Settings["tabsize"].(float64))) - v.hScrollInfo[i-v.Topline] = &scrollInfo{left, offset, style} - } -} - -func (v *View) SetLeftCol(n int) { - v.leftCol = n - v.calcHScrollInfo() -} - func (v *View) paste(clip string) { leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y)) @@ -192,7 +169,7 @@ func (v *View) paste(clip string) { } clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1) v.Buf.Insert(v.Cursor.Loc, clip) - v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf) + // v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf) v.freshClip = false messenger.Message("Pasted clipboard") } @@ -222,18 +199,19 @@ func (v *View) ScrollDown(n int) { // causing them to lose the unsaved changes func (v *View) CanClose() bool { if v.Type == vtDefault && v.Buf.IsModified { - var char rune + var choice bool var canceled bool if v.Buf.Settings["autosave"].(bool) { - char = 'y' + choice = true } else { - char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.GetName()+" before closing? (y,n,esc) ", 'y', 'n') + choice, canceled = messenger.YesNoPrompt("Save changes to " + v.Buf.GetName() + " before closing? (y,n,esc) ") } if !canceled { - if char == 'y' { + //if char == 'y' { + if choice { v.Save(true) return true - } else if char == 'n' { + } else { return true } } @@ -251,8 +229,7 @@ func (v *View) OpenBuffer(buf *Buffer) { v.Buf = buf v.Cursor = &buf.Cursor v.Topline = 0 - // v.leftCol = 0 - v.SetLeftCol(0) + v.leftCol = 0 v.Cursor.ResetSelection() v.Relocate() v.Center(false) @@ -269,15 +246,22 @@ func (v *View) Open(filename string) { home, _ := homedir.Dir() filename = strings.Replace(filename, "~", home, 1) file, err := os.Open(filename) + fileInfo, _ := os.Stat(filename) + + if err == nil && fileInfo.IsDir() { + messenger.Error(filename, " is a directory") + return + } + defer file.Close() var buf *Buffer if err != nil { messenger.Message(err.Error()) // File does not exist -- create an empty buffer with that name - buf = NewBuffer(strings.NewReader(""), filename) + buf = NewBufferFromString("", filename) } else { - buf = NewBuffer(file, filename) + buf = NewBuffer(file, FSize(file), filename) } v.OpenBuffer(buf) } @@ -431,13 +415,11 @@ func (v *View) Relocate() bool { if !v.Buf.Settings["softwrap"].(bool) { cx := v.Cursor.GetVisualX() if cx < v.leftCol { - // v.leftCol = cx - v.SetLeftCol(cx) + v.leftCol = cx ret = true } if cx+v.lineNumOffset+1 > v.leftCol+v.Width { - // v.leftCol = cx - v.Width + v.lineNumOffset + 1 - v.SetLeftCol(cx - v.Width + v.lineNumOffset + 1) + v.leftCol = cx - v.Width + v.lineNumOffset + 1 ret = true } } @@ -468,6 +450,35 @@ func (v *View) MoveToMouseClick(x, y int) { v.Cursor.LastVisualX = v.Cursor.GetVisualX() } +func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool { + relocate := false + readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"} + for _, action := range actions { + readonlyBindingsResult := false + funcName := ShortFuncName(action) + if v.Type.readonly == true { + // check for readonly and if true only let key bindings get called if they do not change the contents. + for _, readonlyBindings := range readonlyBindingsList { + if strings.Contains(funcName, readonlyBindings) { + readonlyBindingsResult = true + } + } + } + if !readonlyBindingsResult { + // call the key binding + relocate = action(v, true) || relocate + // Macro + if funcName != "ToggleMacro" && funcName != "PlayMacro" { + if recordingMacro { + curMacro = append(curMacro, action) + } + } + } + } + + return relocate +} + // HandleEvent handles an event passed by the main loop func (v *View) HandleEvent(event tcell.Event) { // This bool determines whether the view is relocated at the end of the function @@ -480,123 +491,102 @@ func (v *View) HandleEvent(event tcell.Event) { case *tcell.EventKey: // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune isBinding := false - if e.Key() != tcell.KeyRune || e.Modifiers() != 0 { - for key, actions := range bindings { - if e.Key() == key.keyCode { - if e.Key() == tcell.KeyRune { - if e.Rune() != key.r { - continue - } + for key, actions := range bindings { + if e.Key() == key.keyCode { + if e.Key() == tcell.KeyRune { + if e.Rune() != key.r { + continue } - if e.Modifiers() == key.modifiers { + } + if e.Modifiers() == key.modifiers { + for _, c := range v.Buf.cursors { + v.Cursor = c relocate = false isBinding = true - for _, action := range actions { - relocate = action(v, true) || relocate - funcName := FuncName(action) - if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" { - if recordingMacro { - curMacro = append(curMacro, action) - } - } - } - break + relocate = v.ExecuteActions(actions) || relocate } + v.Cursor = &v.Buf.Cursor + break } } } if !isBinding && e.Key() == tcell.KeyRune { - // Insert a character - if v.Cursor.HasSelection() { - v.Cursor.DeleteSelection() - v.Cursor.ResetSelection() - } - v.Buf.Insert(v.Cursor.Loc, string(e.Rune())) - v.Cursor.Right() + // Check viewtype if readonly don't insert a rune (readonly help and log view etc.) + if v.Type.readonly == false { + for _, c := range v.Buf.cursors { + v.Cursor = c + + // Insert a character + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() + } + v.Buf.Insert(v.Cursor.Loc, string(e.Rune())) - for pl := range loadedPlugins { - _, err := Call(pl+".onRune", string(e.Rune()), v) - if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { - TermMessage(err) - } - } + for pl := range loadedPlugins { + _, err := Call(pl+".onRune", string(e.Rune()), v) + if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { + TermMessage(err) + } + } - if recordingMacro { - curMacro = append(curMacro, e.Rune()) + if recordingMacro { + curMacro = append(curMacro, e.Rune()) + } + } + v.Cursor = &v.Buf.Cursor } } case *tcell.EventPaste: - if !PreActionCall("Paste", v) { - break - } + // Check viewtype if readonly don't paste (readonly help and log view etc.) + if v.Type.readonly == false { + if !PreActionCall("Paste", v) { + break + } - v.paste(e.Text()) + for _, c := range v.Buf.cursors { + v.Cursor = c + v.paste(e.Text()) + + } + v.Cursor = &v.Buf.Cursor - PostActionCall("Paste", v) + PostActionCall("Paste", v) + } case *tcell.EventMouse: - x, y := e.Position() - x -= v.lineNumOffset - v.leftCol + v.x - y += v.Topline - v.y // Don't relocate for mouse events relocate = false button := e.Buttons() - switch button { - case tcell.Button1: - // Left click - if v.mouseReleased { - v.MoveToMouseClick(x, y) - if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold { - if v.doubleClick { - // Triple click - v.lastClickTime = time.Now() - - v.tripleClick = true - v.doubleClick = false - - v.Cursor.SelectLine() - v.Cursor.CopySelection("primary") - } else { - // Double click - v.lastClickTime = time.Now() - - v.doubleClick = true - v.tripleClick = false - - v.Cursor.SelectWord() - v.Cursor.CopySelection("primary") - } - } else { - v.doubleClick = false - v.tripleClick = false - v.lastClickTime = time.Now() - - v.Cursor.OrigSelection[0] = v.Cursor.Loc - v.Cursor.CurSelection[0] = v.Cursor.Loc - v.Cursor.CurSelection[1] = v.Cursor.Loc + for key, actions := range bindings { + if button == key.buttons && e.Modifiers() == key.modifiers { + for _, c := range v.Buf.cursors { + v.Cursor = c + relocate = v.ExecuteActions(actions) || relocate } - v.mouseReleased = false - } else if !v.mouseReleased { - v.MoveToMouseClick(x, y) - if v.tripleClick { - v.Cursor.AddLineToSelection() - } else if v.doubleClick { - v.Cursor.AddWordToSelection() - } else { - v.Cursor.SetSelectionEnd(v.Cursor.Loc) - v.Cursor.CopySelection("primary") + v.Cursor = &v.Buf.Cursor + } + } + + for key, actions := range mouseBindings { + if button == key.buttons && e.Modifiers() == key.modifiers { + for _, action := range actions { + action(v, true, e) } } - case tcell.Button2: - // Middle mouse button was clicked, - // We should paste primary - v.PastePrimary(true) + } + + switch button { case tcell.ButtonNone: // Mouse event with no click if !v.mouseReleased { // Mouse was just released + x, y := e.Position() + x -= v.lineNumOffset - v.leftCol + v.x + y += v.Topline - v.y + // Relocating here isn't really necessary because the cursor will // be in the right place from the last mouse event // However, if we are running in a terminal that doesn't support mouse motion @@ -610,19 +600,17 @@ func (v *View) HandleEvent(event tcell.Event) { } v.mouseReleased = true } - case tcell.WheelUp: - // Scroll up - scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64)) - v.ScrollUp(scrollspeed) - case tcell.WheelDown: - // Scroll down - scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64)) - v.ScrollDown(scrollspeed) } } if relocate { v.Relocate() + // We run relocate again because there's a bug with relocating with softwrap + // when for example you jump to the bottom of the buffer and it tries to + // calculate where to put the topline so that the bottom line is at the bottom + // of the terminal and it runs into problems with visual lines vs real lines. + // This is (hopefully) a temporary solution + v.Relocate() } } @@ -662,7 +650,7 @@ func (v *View) openHelp(helpPage string) { if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil { TermMessage("Unable to load help text", helpPage, "\n", err) } else { - helpBuffer := NewBuffer(strings.NewReader(string(data)), helpPage+".md") + helpBuffer := NewBufferFromString(string(data), helpPage+".md") helpBuffer.name = "Help" if v.Type == vtHelp { @@ -675,6 +663,10 @@ func (v *View) openHelp(helpPage string) { } func (v *View) DisplayView() { + if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 { + v.leftCol = 0 + } + if v.Type == vtLog { // Log views should always follow the cursor... v.Relocate() @@ -702,9 +694,11 @@ func (v *View) DisplayView() { v.lineNumOffset += 2 } + divider := 0 if v.x != 0 { // One space for the extra split divider v.lineNumOffset++ + divider = 1 } xOffset := v.x + v.lineNumOffset @@ -715,7 +709,7 @@ func (v *View) DisplayView() { left := v.leftCol top := v.Topline - v.cellview.Draw(v.Buf, v.hScrollInfo, top, height, left, width-v.lineNumOffset) + v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset) screenX := v.x realLineN := top - 1 @@ -737,14 +731,16 @@ func (v *View) DisplayView() { realLineN++ } - screenX = v.x - - if v.x != 0 { - // Draw the split divider - screen.SetContent(screenX, yOffset+visualLineN, '|', nil, defStyle.Reverse(true)) - screenX++ + colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64)) + if colorcolumn != 0 { + style := GetColor("color-column") + fg, _, _ := style.Decompose() + st := defStyle.Background(fg) + screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st) } + screenX = v.x + // If there are gutter messages we need to display the '>>' symbol here if hasGutterMessages { // msgOnLine stores whether or not there is a gutter message on this line in particular @@ -808,25 +804,25 @@ func (v *View) DisplayView() { // Write the spaces before the line number if necessary for i := 0; i < maxLineNumLength-len(lineNum); i++ { - screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle) + screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle) screenX++ } if softwrapped && visualLineN != 0 { // Pad without the line number because it was written on the visual line before for range lineNum { - screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle) + screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle) screenX++ } } else { // Write the actual line number for _, ch := range lineNum { - screen.SetContent(screenX, yOffset+visualLineN, ch, nil, lineNumStyle) + screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle) screenX++ } } // Write the extra space - screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle) + screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle) screenX++ } @@ -836,23 +832,28 @@ func (v *View) DisplayView() { if char != nil { lineStyle := char.style - charLoc := char.realLoc - if v.Cursor.HasSelection() && - (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) || - charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) { - // The current character is selected - lineStyle = defStyle.Reverse(true) - - if style, ok := colorscheme["selection"]; ok { - lineStyle = style - } + colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64)) + if colorcolumn != 0 && char.visualLoc.X == colorcolumn { + style := GetColor("color-column") + fg, _, _ := style.Decompose() + lineStyle = lineStyle.Background(fg) } - if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && - v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && !cursorSet { - screen.ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y) - cursorSet = true + charLoc := char.realLoc + for _, c := range v.Buf.cursors { + v.Cursor = c + if v.Cursor.HasSelection() && + (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) || + charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) { + // The current character is selected + lineStyle = defStyle.Reverse(true) + + if style, ok := colorscheme["selection"]; ok { + lineStyle = style + } + } } + v.Cursor = &v.Buf.Cursor if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == realLineN { @@ -863,6 +864,16 @@ func (v *View) DisplayView() { screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle) + for i, c := range v.Buf.cursors { + v.Cursor = c + if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && + v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) { + ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i) + cursorSet = true + } + } + v.Cursor = &v.Buf.Cursor + lastChar = char } } @@ -870,19 +881,30 @@ func (v *View) DisplayView() { lastX := 0 var realLoc Loc var visualLoc Loc + var cx, cy int if lastChar != nil { lastX = xOffset + lastChar.visualLoc.X + lastChar.width - if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && - v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 { - screen.ShowCursor(lastX, yOffset+lastChar.visualLoc.Y) + for i, c := range v.Buf.cursors { + v.Cursor = c + if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && + v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 { + ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i) + cx, cy = lastX, yOffset+lastChar.visualLoc.Y + } } - realLoc = Loc{lastChar.realLoc.X, realLineN} + v.Cursor = &v.Buf.Cursor + realLoc = Loc{lastChar.realLoc.X + 1, realLineN} visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y} } else if len(line) == 0 { - if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && - v.Cursor.Y == realLineN { - screen.ShowCursor(xOffset, yOffset+visualLineN) + for i, c := range v.Buf.cursors { + v.Cursor = c + if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && + v.Cursor.Y == realLineN { + ShowMultiCursor(xOffset, yOffset+visualLineN, i) + cx, cy = xOffset, yOffset+visualLineN + } } + v.Cursor = &v.Buf.Cursor lastX = xOffset realLoc = Loc{0, realLineN} visualLoc = Loc{0, visualLineN} @@ -906,29 +928,44 @@ func (v *View) DisplayView() { style := GetColor("cursor-line") fg, _, _ := style.Decompose() style = style.Background(fg) - screen.SetContent(i, yOffset+visualLineN, ' ', nil, style) + if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) { + screen.SetContent(i, yOffset+visualLineN, ' ', nil, style) + } } } } - if v.x != 0 && visualLineN < v.Height { - for i := visualLineN + 1; i < v.Height; i++ { - screen.SetContent(v.x, yOffset+i, '|', nil, defStyle.Reverse(true)) + if divider != 0 { + dividerStyle := defStyle + if style, ok := colorscheme["divider"]; ok { + dividerStyle = style + } + for i := 0; i < v.Height; i++ { + screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true)) } } } -// DisplayCursor draws the current buffer's cursor to the screen -func (v *View) DisplayCursor(x, y int) { - // screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, y) - screen.ShowCursor(x, y) +// ShowMultiCursor will display a cursor at a location +// If i == 0 then the terminal cursor will be used +// Otherwise a fake cursor will be drawn at the position +func ShowMultiCursor(x, y, i int) { + if i == 0 { + screen.ShowCursor(x, y) + } else { + r, _, _, _ := screen.GetContent(x, y) + screen.SetContent(x, y, r, nil, defStyle.Reverse(true)) + } } // Display renders the view, the cursor, and statusline func (v *View) Display() { + if globalSettings["termtitle"].(bool) { + screen.SetTitle("micro: " + v.Buf.GetName()) + } v.DisplayView() // Don't draw the cursor if it is out of the viewport or if it has a selection - if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1) || v.Cursor.HasSelection() { + if v.Num == tabs[curTab].CurView && (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1 || v.Cursor.HasSelection()) { screen.HideCursor() } _, screenH := screen.Size()