11 "github.com/zyedidia/tcell"
14 // The ViewType defines what kind of view this is
15 type ViewType struct {
17 Readonly bool // The file cannot be edited
18 Scratch bool // The file cannot be saved
22 vtDefault = ViewType{0, false, false}
23 vtHelp = ViewType{1, true, true}
24 vtLog = ViewType{2, true, true}
25 vtScratch = ViewType{3, false, true}
26 vtRaw = ViewType{4, true, true}
29 // The View struct stores information about a view into a buffer.
30 // It stores information about the cursor, and the viewport
31 // that the user sees the buffer from.
33 // A pointer to the buffer's cursor for ease of access
36 // The topmost line, used for vertical scrolling
38 // The leftmost column, used for horizontal scrolling
41 // Specifies whether or not this view holds a help buffer
44 // Actual width and height
51 // Where this view is located
54 // How much to offset because of line numbers
57 // Holds the list of gutter messages
58 messages map[string][]GutterMessage
60 // This is the index of this view in the views array
62 // What tab is this view stored in
70 // Since tcell doesn't differentiate between a mouse release event
71 // and a mouse move event with no keys pressed, we need to keep
72 // track of whether or not the mouse was pressed (or not released) last event to determine
73 // mouse release events
76 // We need to keep track of insert key press toggle
78 // This stores when the last click was
79 // This is useful for detecting double and triple clicks
80 lastClickTime time.Time
83 // lastCutTime stores when the last ctrl+k was issued.
84 // It is used for clearing the clipboard to replace it with fresh cut lines.
87 // freshClip returns true if the clipboard has never been pasted.
90 // Was the last mouse event actually a double click?
91 // Useful for detecting triple clicks -- if a double click is detected
92 // but the last mouse event was actually a double click, it's a triple click
94 // Same here, just to keep track for mouse move events
104 // NewView returns a new fullscreen view
105 func NewView(buf *Buffer) *View {
106 screenW, screenH := screen.Size()
107 return NewViewWidthHeight(buf, screenW, screenH)
110 // NewViewWidthHeight returns a new view with the specified width and height
111 // Note that w and h are raw column and row values
112 func NewViewWidthHeight(buf *Buffer, w, h int) *View {
119 v.cellview = new(CellView)
125 v.messages = make(map[string][]GutterMessage)
127 v.sline = &Statusline{
131 v.scrollbar = &ScrollBar{
135 if v.Buf.Settings["statusline"].(bool) {
139 for pl := range loadedPlugins {
140 _, err := Call(pl+".onViewOpen", v)
141 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
150 // ToggleStatusLine creates an extra row for the statusline if necessary
151 func (v *View) ToggleStatusLine() {
152 if v.Buf.Settings["statusline"].(bool) {
159 // ToggleTabbar creates an extra row for the tabbar if necessary
160 func (v *View) ToggleTabbar() {
163 // Include one line for the tab bar at the top
175 func (v *View) paste(clip string) {
176 leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
178 if v.Cursor.HasSelection() {
179 v.Cursor.DeleteSelection()
180 v.Cursor.ResetSelection()
182 clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
183 v.Buf.Insert(v.Cursor.Loc, clip)
184 // v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
186 messenger.Message("Pasted clipboard")
189 // ScrollUp scrolls the view up n lines (if possible)
190 func (v *View) ScrollUp(n int) {
191 // Try to scroll by n but if it would overflow, scroll by 1
192 if v.Topline-n >= 0 {
194 } else if v.Topline > 0 {
199 // ScrollDown scrolls the view down n lines (if possible)
200 func (v *View) ScrollDown(n int) {
201 // Try to scroll by n but if it would overflow, scroll by 1
202 if v.Topline+n <= v.Buf.NumLines {
204 } else if v.Topline < v.Buf.NumLines-1 {
209 // CanClose returns whether or not the view can be closed
210 // If there are unsaved changes, the user will be asked if the view can be closed
211 // causing them to lose the unsaved changes
212 func (v *View) CanClose() bool {
213 if v.Type == vtDefault && v.Buf.Modified() {
216 if v.Buf.Settings["autosave"].(bool) {
219 choice, canceled = messenger.YesNoPrompt("Save changes to " + v.Buf.GetName() + " before closing? (y,n,esc) ")
233 // OpenBuffer opens a new buffer in this view.
234 // This resets the topline, event handler and cursor.
235 func (v *View) OpenBuffer(buf *Buffer) {
239 v.Cursor = &buf.Cursor
242 v.Cursor.ResetSelection()
245 v.messages = make(map[string][]GutterMessage)
247 // Set mouseReleased to true because we assume the mouse is not being pressed when
248 // the editor is opened
249 v.mouseReleased = true
250 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
252 v.isOverwriteMode = false
253 v.lastClickTime = time.Time{}
256 // Open opens the given file in the view
257 func (v *View) Open(filename string) {
258 filename = ReplaceHome(filename)
259 file, err := os.Open(filename)
260 fileInfo, _ := os.Stat(filename)
262 if err == nil && fileInfo.IsDir() {
263 messenger.Error(filename, " is a directory")
271 messenger.Message(err.Error())
272 // File does not exist -- create an empty buffer with that name
273 buf = NewBufferFromString("", filename)
275 buf = NewBuffer(file, FSize(file), filename)
280 // CloseBuffer performs any closing functions on the buffer
281 func (v *View) CloseBuffer() {
287 // ReOpen reloads the current buffer
288 func (v *View) ReOpen() {
296 // HSplit opens a horizontal split with the given buffer
297 func (v *View) HSplit(buf *Buffer) {
299 if v.Buf.Settings["splitbottom"].(bool) {
302 v.splitNode.HSplit(buf, v.Num+i)
305 // VSplit opens a vertical split with the given buffer
306 func (v *View) VSplit(buf *Buffer) {
308 if v.Buf.Settings["splitright"].(bool) {
311 v.splitNode.VSplit(buf, v.Num+i)
314 // HSplitIndex opens a horizontal split with the given buffer at the given index
315 func (v *View) HSplitIndex(buf *Buffer, splitIndex int) {
316 v.splitNode.HSplit(buf, splitIndex)
319 // VSplitIndex opens a vertical split with the given buffer at the given index
320 func (v *View) VSplitIndex(buf *Buffer, splitIndex int) {
321 v.splitNode.VSplit(buf, splitIndex)
324 // GetSoftWrapLocation gets the location of a visual click on the screen and converts it to col,line
325 func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
326 if !v.Buf.Settings["softwrap"].(bool) {
327 if vy >= v.Buf.NumLines {
328 vy = v.Buf.NumLines - 1
330 vx = v.Cursor.GetCharPosInLine(vy, vx)
334 screenX, screenY := 0, v.Topline
335 for lineN := v.Topline; lineN < v.Bottomline(); lineN++ {
336 line := v.Buf.Line(lineN)
337 if lineN >= v.Buf.NumLines {
338 return 0, v.Buf.NumLines - 1
342 for _, ch := range line {
343 if screenX >= v.Width-v.lineNumOffset {
348 if screenX == vx && screenY == vy {
353 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
369 func (v *View) Bottomline() int {
370 if !v.Buf.Settings["softwrap"].(bool) {
371 return v.Topline + v.Height
374 screenX, screenY := 0, 0
376 for lineN := v.Topline; lineN < v.Topline+v.Height; lineN++ {
377 line := v.Buf.Line(lineN)
380 for _, ch := range line {
381 if screenX >= v.Width-v.lineNumOffset {
387 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
397 if screenY >= v.Height {
401 return numLines + v.Topline
404 // Relocate moves the view window so that the cursor is in view
405 // This is useful if the user has scrolled far away, and then starts typing
406 func (v *View) Relocate() bool {
407 height := v.Bottomline() - v.Topline
410 scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64))
411 if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
412 v.Topline = cy - scrollmargin
414 } else if cy < v.Topline {
418 if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
419 v.Topline = cy - height + 1 + scrollmargin
421 } else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
422 v.Topline = v.Buf.NumLines - height
426 if !v.Buf.Settings["softwrap"].(bool) {
427 cx := v.Cursor.GetVisualX()
432 if cx+v.lineNumOffset+1 > v.leftCol+v.Width {
433 v.leftCol = cx - v.Width + v.lineNumOffset + 1
440 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
442 func (v *View) MoveToMouseClick(x, y int) {
443 if y-v.Topline > v.Height-1 {
445 y = v.Height + v.Topline - 1
454 x, y = v.GetSoftWrapLocation(x, y)
455 // x = v.Cursor.GetCharPosInLine(y, x)
456 if x > Count(v.Buf.Line(y)) {
457 x = Count(v.Buf.Line(y))
461 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
464 // Execute actions executes the supplied actions
465 func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
467 readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
468 for _, action := range actions {
469 readonlyBindingsResult := false
470 funcName := ShortFuncName(action)
471 if v.Type.Readonly == true {
472 // check for readonly and if true only let key bindings get called if they do not change the contents.
473 for _, readonlyBindings := range readonlyBindingsList {
474 if strings.Contains(funcName, readonlyBindings) {
475 readonlyBindingsResult = true
479 if !readonlyBindingsResult {
480 // call the key binding
481 relocate = action(v, true) || relocate
483 if funcName != "ToggleMacro" && funcName != "PlayMacro" {
485 curMacro = append(curMacro, action)
494 // SetCursor sets the view's and buffer's cursor
495 func (v *View) SetCursor(c *Cursor) bool {
500 v.Buf.curCursor = c.Num
505 // HandleEvent handles an event passed by the main loop
506 func (v *View) HandleEvent(event tcell.Event) {
508 v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
509 v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
511 switch e := event.(type) {
512 case *tcell.EventKey:
513 if e.Key() == tcell.KeyCtrlQ {
521 // This bool determines whether the view is relocated at the end of the function
522 // By default it's true because most events should cause a relocate
527 switch e := event.(type) {
528 case *tcell.EventRaw:
529 for key, actions := range bindings {
530 if key.keyCode == -1 {
531 if e.EscSeq() == key.escape {
532 for _, c := range v.Buf.cursors {
538 relocate = v.ExecuteActions(actions) || relocate
540 v.SetCursor(&v.Buf.Cursor)
546 case *tcell.EventKey:
547 // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
549 for key, actions := range bindings {
550 if e.Key() == key.keyCode {
551 if e.Key() == tcell.KeyRune {
552 if e.Rune() != key.r {
556 if e.Modifiers() == key.modifiers {
557 for _, c := range v.Buf.cursors {
564 relocate = v.ExecuteActions(actions) || relocate
566 v.SetCursor(&v.Buf.Cursor)
573 if !isBinding && e.Key() == tcell.KeyRune {
574 // Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
575 if v.Type.Readonly == false {
576 for _, c := range v.Buf.cursors {
579 // Insert a character
580 if v.Cursor.HasSelection() {
581 v.Cursor.DeleteSelection()
582 v.Cursor.ResetSelection()
585 if v.isOverwriteMode {
588 v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
590 v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
593 for pl := range loadedPlugins {
594 _, err := Call(pl+".onRune", string(e.Rune()), v)
595 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
601 curMacro = append(curMacro, e.Rune())
604 v.SetCursor(&v.Buf.Cursor)
607 case *tcell.EventPaste:
608 // Check viewtype if readonly don't paste (readonly help and log view etc.)
609 if v.Type.Readonly == false {
610 if !PreActionCall("Paste", v) {
614 for _, c := range v.Buf.cursors {
618 v.SetCursor(&v.Buf.Cursor)
620 PostActionCall("Paste", v)
622 case *tcell.EventMouse:
623 // Don't relocate for mouse events
626 button := e.Buttons()
628 for key, actions := range bindings {
629 if button == key.buttons && e.Modifiers() == key.modifiers {
630 for _, c := range v.Buf.cursors {
635 relocate = v.ExecuteActions(actions) || relocate
637 v.SetCursor(&v.Buf.Cursor)
642 for key, actions := range mouseBindings {
643 if button == key.buttons && e.Modifiers() == key.modifiers {
644 for _, action := range actions {
651 case tcell.ButtonNone:
652 // Mouse event with no click
653 if !v.mouseReleased {
654 // Mouse was just released
657 x -= v.lineNumOffset - v.leftCol + v.x
660 // Relocating here isn't really necessary because the cursor will
661 // be in the right place from the last mouse event
662 // However, if we are running in a terminal that doesn't support mouse motion
663 // events, this still allows the user to make selections, except only after they
666 if !v.doubleClick && !v.tripleClick {
667 v.MoveToMouseClick(x, y)
668 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
669 v.Cursor.CopySelection("primary")
671 v.mouseReleased = true
678 // We run relocate again because there's a bug with relocating with softwrap
679 // when for example you jump to the bottom of the buffer and it tries to
680 // calculate where to put the topline so that the bottom line is at the bottom
681 // of the terminal and it runs into problems with visual lines vs real lines.
682 // This is (hopefully) a temporary solution
687 func (v *View) mainCursor() bool {
688 return v.Buf.curCursor == len(v.Buf.cursors)-1
691 // GutterMessage creates a message in this view's gutter
692 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
694 gutterMsg := GutterMessage{
699 for _, v := range v.messages {
700 for _, gmsg := range v {
701 if gmsg.lineNum == lineN {
706 messages := v.messages[section]
707 v.messages[section] = append(messages, gutterMsg)
710 // ClearGutterMessages clears all gutter messages from a given section
711 func (v *View) ClearGutterMessages(section string) {
712 v.messages[section] = []GutterMessage{}
715 // ClearAllGutterMessages clears all the gutter messages
716 func (v *View) ClearAllGutterMessages() {
717 for k := range v.messages {
718 v.messages[k] = []GutterMessage{}
722 // Opens the given help page in a new horizontal split
723 func (v *View) openHelp(helpPage string) {
724 if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
725 TermMessage("Unable to load help text", helpPage, "\n", err)
727 helpBuffer := NewBufferFromString(string(data), helpPage+".md")
728 helpBuffer.name = "Help"
730 if v.Type == vtHelp {
731 v.OpenBuffer(helpBuffer)
734 CurView().Type = vtHelp
739 // DisplayView draws the view to the screen
740 func (v *View) DisplayView() {
741 if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
745 if v.Type == vtLog || v.Type == vtRaw {
746 // Log or raw views should always follow the cursor...
750 // We need to know the string length of the largest line number
751 // so we can pad appropriately when displaying line numbers
752 maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))
754 if v.Buf.Settings["ruler"] == true {
755 // + 1 for the little space after the line number
756 v.lineNumOffset = maxLineNumLength + 1
761 // We need to add to the line offset if there are gutter messages
762 var hasGutterMessages bool
763 for _, v := range v.messages {
765 hasGutterMessages = true
768 if hasGutterMessages {
774 // One space for the extra split divider
779 xOffset := v.x + v.lineNumOffset
787 v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)
793 for visualLineN, line = range v.cellview.lines {
800 if firstChar != nil {
801 if firstChar.realLoc.Y == realLineN {
804 realLineN = firstChar.realLoc.Y
809 colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
810 if colorcolumn != 0 {
811 style := GetColor("color-column")
812 fg, _, _ := style.Decompose()
813 st := defStyle.Background(fg)
814 screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
819 // If there are gutter messages we need to display the '>>' symbol here
820 if hasGutterMessages {
821 // msgOnLine stores whether or not there is a gutter message on this line in particular
823 for k := range v.messages {
824 for _, msg := range v.messages[k] {
825 if msg.lineNum == realLineN {
827 gutterStyle := defStyle
830 if style, ok := colorscheme["gutter-info"]; ok {
834 if style, ok := colorscheme["gutter-warning"]; ok {
838 if style, ok := colorscheme["gutter-error"]; ok {
842 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
844 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
846 if v.Cursor.Y == realLineN && !messenger.hasPrompt {
847 messenger.Message(msg.msg)
848 messenger.gutterMessage = true
853 // If there is no message on this line we just display an empty offset
855 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
857 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
859 if v.Cursor.Y == realLineN && messenger.gutterMessage {
861 messenger.gutterMessage = false
866 lineNumStyle := defStyle
867 if v.Buf.Settings["ruler"] == true {
868 // Write the line number
869 if style, ok := colorscheme["line-number"]; ok {
872 if style, ok := colorscheme["current-line-number"]; ok {
873 if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
878 lineNum := strconv.Itoa(realLineN + 1)
880 // Write the spaces before the line number if necessary
881 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
882 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
885 if softwrapped && visualLineN != 0 {
886 // Pad without the line number because it was written on the visual line before
888 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
892 // Write the actual line number
893 for _, ch := range lineNum {
894 screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle)
899 // Write the extra space
900 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
906 for _, char := range line {
908 lineStyle := char.style
910 colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
911 if colorcolumn != 0 && char.visualLoc.X == colorcolumn {
912 style := GetColor("color-column")
913 fg, _, _ := style.Decompose()
914 lineStyle = lineStyle.Background(fg)
917 charLoc := char.realLoc
918 for _, c := range v.Buf.cursors {
920 if v.Cursor.HasSelection() &&
921 (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
922 charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
923 // The current character is selected
924 lineStyle = defStyle.Reverse(true)
926 if style, ok := colorscheme["selection"]; ok {
931 v.SetCursor(&v.Buf.Cursor)
933 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
934 !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
935 style := GetColor("cursor-line")
936 fg, _, _ := style.Decompose()
937 lineStyle = lineStyle.Background(fg)
940 screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
942 for i, c := range v.Buf.cursors {
944 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
945 v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) {
946 ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
950 v.SetCursor(&v.Buf.Cursor)
961 lastX = xOffset + lastChar.visualLoc.X + lastChar.width
962 for i, c := range v.Buf.cursors {
964 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
965 v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
966 ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
967 cx, cy = lastX, yOffset+lastChar.visualLoc.Y
970 v.SetCursor(&v.Buf.Cursor)
971 realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
972 visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
973 } else if len(line) == 0 {
974 for i, c := range v.Buf.cursors {
976 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
977 v.Cursor.Y == realLineN {
978 ShowMultiCursor(xOffset, yOffset+visualLineN, i)
979 cx, cy = xOffset, yOffset+visualLineN
982 v.SetCursor(&v.Buf.Cursor)
984 realLoc = Loc{0, realLineN}
985 visualLoc = Loc{0, visualLineN}
988 if v.Cursor.HasSelection() &&
989 (realLoc.GreaterEqual(v.Cursor.CurSelection[0]) && realLoc.LessThan(v.Cursor.CurSelection[1]) ||
990 realLoc.LessThan(v.Cursor.CurSelection[0]) && realLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
991 // The current character is selected
992 selectStyle := defStyle.Reverse(true)
994 if style, ok := colorscheme["selection"]; ok {
997 screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle)
1000 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
1001 !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
1002 for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
1003 style := GetColor("cursor-line")
1004 fg, _, _ := style.Decompose()
1005 style = style.Background(fg)
1006 if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) {
1007 screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
1014 dividerStyle := defStyle
1015 if style, ok := colorscheme["divider"]; ok {
1016 dividerStyle = style
1018 for i := 0; i < v.Height; i++ {
1019 screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true))
1024 // ShowMultiCursor will display a cursor at a location
1025 // If i == 0 then the terminal cursor will be used
1026 // Otherwise a fake cursor will be drawn at the position
1027 func ShowMultiCursor(x, y, i int) {
1029 screen.ShowCursor(x, y)
1031 r, _, _, _ := screen.GetContent(x, y)
1032 screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
1036 // Display renders the view, the cursor, and statusline
1037 func (v *View) Display() {
1038 if globalSettings["termtitle"].(bool) {
1039 screen.SetTitle("micro: " + v.Buf.GetName())
1042 // Don't draw the cursor if it is out of the viewport or if it has a selection
1043 if v.Num == tabs[curTab].CurView && (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1 || v.Cursor.HasSelection()) {
1046 _, screenH := screen.Size()
1048 if v.Buf.Settings["scrollbar"].(bool) {
1049 v.scrollbar.Display()
1052 if v.Buf.Settings["statusline"].(bool) {
1054 } else if (v.y + v.Height) != screenH-1 {
1055 for x := 0; x < v.Width; x++ {
1056 screen.SetContent(v.x+x, v.y+v.Height, '-', nil, defStyle.Reverse(true))