X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=cmd%2Fmicro%2Fview.go;h=c05d83344cea8d37dea84fd00ee24731720699c0;hb=55add69fa01025c37c8a6dbd2014ced24d612e09;hp=e1d197a9f53c6271ead2f4cb0b5a89ca511fbdce;hpb=8f06e51170ea86bd05065539727f3e8a12f6b6b3;p=micro.git diff --git a/cmd/micro/view.go b/cmd/micro/view.go index e1d197a9..c05d8334 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -11,6 +11,14 @@ import ( "github.com/zyedidia/tcell" ) +type ViewType int + +const ( + vtDefault ViewType = iota + vtHelp + vtLog +) + // 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. @@ -23,14 +31,10 @@ type View struct { // The leftmost column, used for horizontal scrolling leftCol int - // Percentage of the terminal window that this view takes up (from 0 to 100) - widthPercent int - heightPercent int - // Specifies whether or not this view holds a help buffer - Help bool + Type ViewType - // Actual with and height + // Actual width and height width int height int @@ -124,6 +128,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View { return v } +// ToggleStatusLine creates an extra row for the statusline if necessary func (v *View) ToggleStatusLine() { if v.Buf.Settings["statusline"].(bool) { v.height-- @@ -132,6 +137,7 @@ func (v *View) ToggleStatusLine() { } } +// ToggleTabbar creates an extra row for the tabbar if necessary func (v *View) ToggleTabbar() { if len(tabs) > 1 { if v.y == 0 { @@ -185,8 +191,14 @@ func (v *View) ScrollDown(n int) { // If there are unsaved changes, the user will be asked if the view can be closed // causing them to lose the unsaved changes func (v *View) CanClose() bool { - if v.Buf.IsModified { - char, canceled := messenger.LetterPrompt("Save changes to "+v.Buf.Name+" before closing? (y,n,esc) ", 'y', 'n') + if v.Type == vtDefault && v.Buf.IsModified { + var char rune + var canceled bool + if v.Buf.Settings["autosave"].(bool) { + char = 'y' + } else { + char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.Name+" before closing? (y,n,esc) ", 'y', 'n') + } if !canceled { if char == 'y' { v.Save(true) @@ -223,6 +235,7 @@ func (v *View) OpenBuffer(buf *Buffer) { v.lastClickTime = time.Time{} } +// Open opens the given file in the view func (v *View) Open(filename string) { home, _ := homedir.Dir() filename = strings.Replace(filename, "~", home, 1) @@ -270,9 +283,80 @@ func (v *View) VSplit(buf *Buffer) bool { return false } +// GetSoftWrapLocation gets the location of a visual click on the screen and converts it to col,line +func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) { + if !v.Buf.Settings["softwrap"].(bool) { + vx = v.Cursor.GetCharPosInLine(vy, vx) + return vx, vy + } + + screenX, screenY := 0, v.Topline + for lineN := v.Topline; lineN < v.Bottomline(); lineN++ { + line := v.Buf.Line(lineN) + + colN := 0 + for _, ch := range line { + if screenX >= v.width-v.lineNumOffset { + screenX = 0 + screenY++ + } + + if screenX == vx && screenY == vy { + return colN, lineN + } + + if ch == '\t' { + screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1 + } + + screenX++ + colN++ + } + if screenY == vy { + return colN, lineN + } + screenX = 0 + screenY++ + } + + return 0, 0 +} + +func (v *View) Bottomline() int { + screenX, screenY := 0, 0 + numLines := 0 + for lineN := v.Topline; lineN < v.Topline+v.height; lineN++ { + line := v.Buf.Line(lineN) + + colN := 0 + for _, ch := range line { + if screenX >= v.width-v.lineNumOffset { + screenX = 0 + screenY++ + } + + if ch == '\t' { + screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1 + } + + screenX++ + colN++ + } + screenX = 0 + screenY++ + numLines++ + + if screenY >= v.height { + break + } + } + return numLines + v.Topline +} + // 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 func (v *View) Relocate() bool { + height := v.Bottomline() - v.Topline ret := false cy := v.Cursor.Y scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64)) @@ -283,22 +367,24 @@ func (v *View) Relocate() bool { v.Topline = cy ret = true } - if cy > v.Topline+v.height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin { - v.Topline = cy - v.height + 1 + scrollmargin + if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin { + v.Topline = cy - height + 1 + scrollmargin ret = true - } else if cy >= v.Buf.NumLines-scrollmargin && cy > v.height { - v.Topline = v.Buf.NumLines - v.height + } else if cy >= v.Buf.NumLines-scrollmargin && cy > height { + v.Topline = v.Buf.NumLines - height ret = true } - cx := v.Cursor.GetVisualX() - if cx < v.leftCol { - v.leftCol = cx - ret = true - } - if cx+v.lineNumOffset+1 > v.leftCol+v.width { - v.leftCol = cx - v.width + v.lineNumOffset + 1 - ret = true + if !v.Buf.Settings["softwrap"].(bool) { + cx := v.Cursor.GetVisualX() + if cx < v.leftCol { + v.leftCol = cx + ret = true + } + if cx+v.lineNumOffset+1 > v.leftCol+v.width { + v.leftCol = cx - v.width + v.lineNumOffset + 1 + ret = true + } } return ret } @@ -320,7 +406,8 @@ func (v *View) MoveToMouseClick(x, y int) { x = 0 } - x = v.Cursor.GetCharPosInLine(y, x) + x, y = v.GetSoftWrapLocation(x, y) + // x = v.Cursor.GetCharPosInLine(y, x) if x > Count(v.Buf.Line(y)) { x = Count(v.Buf.Line(y)) } @@ -364,6 +451,7 @@ func (v *View) HandleEvent(event tcell.Event) { } } } + break } } } @@ -393,18 +481,7 @@ func (v *View) HandleEvent(event tcell.Event) { break } - leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y)) - - if v.Cursor.HasSelection() { - v.Cursor.DeleteSelection() - v.Cursor.ResetSelection() - } - clip := e.Text() - 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.freshClip = false - messenger.Message("Pasted clipboard") + v.paste(e.Text()) PostActionCall("Paste", v) case *tcell.EventMouse: @@ -494,9 +571,6 @@ func (v *View) HandleEvent(event tcell.Event) { if relocate { v.Relocate() } - if v.Buf.Settings["syntax"].(bool) { - v.matches = Match(v) - } } // GutterMessage creates a message in this view's gutter @@ -532,15 +606,18 @@ func (v *View) ClearAllGutterMessages() { // Opens the given help page in a new horizontal split func (v *View) openHelp(helpPage string) { - if v.Help { - helpBuffer := NewBuffer([]byte(helpPages[helpPage]), helpPage+".md") - helpBuffer.Name = "Help" - v.OpenBuffer(helpBuffer) + if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil { + TermMessage("Unable to load help text", helpPage, "\n", err) } else { - helpBuffer := NewBuffer([]byte(helpPages[helpPage]), helpPage+".md") + helpBuffer := NewBuffer(data, helpPage+".md") helpBuffer.Name = "Help" - v.HSplit(helpBuffer) - CurView().Help = true + + if v.Type == vtHelp { + v.OpenBuffer(helpBuffer) + } else { + v.HSplit(helpBuffer) + CurView().Type = vtHelp + } } } @@ -552,6 +629,15 @@ func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) { // DisplayView renders the view to the screen func (v *View) DisplayView() { + if v.Type == vtLog { + // Log views should always follow the cursor... + v.Relocate() + } + + if v.Buf.Settings["syntax"].(bool) { + v.matches = Match(v) + } + // The charNum we are currently displaying // starts at the start of the viewport charNum := Loc{0, v.Topline} @@ -584,17 +670,22 @@ func (v *View) DisplayView() { } // These represent the current screen coordinates - screenX, screenY := 0, 0 + screenX, screenY := v.x, v.y-1 highlightStyle := defStyle + curLineN := 0 // ViewLine is the current line from the top of the viewport for viewLine := 0; viewLine < v.height; viewLine++ { - screenY = v.y + viewLine + screenY++ screenX = v.x // This is the current line number of the buffer that we are drawing - curLineN := viewLine + v.Topline + curLineN = viewLine + v.Topline + + if screenY-v.y >= v.height { + break + } if v.x != 0 { // Draw the split divider @@ -659,9 +750,9 @@ func (v *View) DisplayView() { } } + lineNumStyle := defStyle if v.Buf.Settings["ruler"] == true { // Write the line number - lineNumStyle := defStyle if style, ok := colorscheme["line-number"]; ok { lineNumStyle = style } @@ -691,7 +782,23 @@ func (v *View) DisplayView() { // Now we actually draw the line colN := 0 + strWidth := 0 + tabSize := int(v.Buf.Settings["tabsize"].(float64)) for _, ch := range line { + if v.Buf.Settings["softwrap"].(bool) { + if screenX-v.x >= v.width { + screenY++ + for i := 0; i < v.lineNumOffset; i++ { + screen.SetContent(v.x+i, screenY, ' ', nil, lineNumStyle) + } + screenX = v.x + v.lineNumOffset + } + } + + if tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X { + v.DisplayCursor(screenX-v.leftCol, screenY) + } + lineStyle := defStyle if v.Buf.Settings["syntax"].(bool) { @@ -727,7 +834,7 @@ func (v *View) DisplayView() { // First the user may have configured an `indent-char` to be displayed to show that this // is a tab character lineIndentStyle := defStyle - if style, ok := colorscheme["indent-char"]; ok { + if style, ok := colorscheme["indent-char"]; ok && v.Buf.Settings["indentchar"].(string) != " " { lineIndentStyle = style } if v.Cursor.HasSelection() && @@ -752,8 +859,9 @@ func (v *View) DisplayView() { v.drawCell(screenX-v.leftCol, screenY, indentChar[0], nil, lineIndentStyle) } // Now the tab has to be displayed as a bunch of spaces - tabSize := int(v.Buf.Settings["tabsize"].(float64)) - for i := 0; i < tabSize-1; i++ { + visLoc := strWidth + remainder := tabSize - (visLoc % tabSize) + for i := 0; i < remainder-1; i++ { screenX++ if screenX-v.x-v.leftCol >= v.lineNumOffset { v.drawCell(screenX-v.leftCol, screenY, ' ', nil, lineStyle) @@ -777,9 +885,14 @@ func (v *View) DisplayView() { charNum = charNum.Move(1, v.Buf) screenX++ colN++ + strWidth += StringWidth(string(ch), tabSize) } // Here we are at a newline + if tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X { + v.DisplayCursor(screenX-v.leftCol, screenY) + } + // The newline may be selected, in which case we should draw the selection style // with a space to represent it if v.Cursor.HasSelection() && @@ -807,35 +920,30 @@ func (v *View) DisplayView() { } if screenX-v.x-v.leftCol+i >= v.lineNumOffset { colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64)) - if colorcolumn != 0 && screenX-v.leftCol+i == colorcolumn-1 { + if colorcolumn != 0 && screenX-v.lineNumOffset+i == colorcolumn-1 { if style, ok := colorscheme["color-column"]; ok { fg, _, _ := style.Decompose() lineStyle = lineStyle.Background(fg) } - v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle) - } else { - v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle) } + v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle) } } } } // DisplayCursor draws the current buffer's cursor to the screen -func (v *View) DisplayCursor() { - // 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() { - screen.HideCursor() - } else { - screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.Y-v.Topline+v.y) - } +func (v *View) DisplayCursor(x, y int) { + // screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, y) + screen.ShowCursor(x, y) } // Display renders the view, the cursor, and statusline func (v *View) Display() { v.DisplayView() - if v.Num == tabs[curTab].curView { - v.DisplayCursor() + // 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() { + screen.HideCursor() } _, screenH := screen.Size() if v.Buf.Settings["statusline"].(bool) {