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 isInsertMode to false, because we assume we are in the default mode when editor
252 v.isInsertMode = 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)
572 if e.Key() == tcell.KeyInsert {
573 v.isInsertMode = !v.isInsertMode
575 if !isBinding && e.Key() == tcell.KeyRune {
576 // Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
577 if v.Type.Readonly == false {
578 for _, c := range v.Buf.cursors {
581 // Insert a character
582 if v.Cursor.HasSelection() {
583 v.Cursor.DeleteSelection()
584 v.Cursor.ResetSelection()
590 v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
592 v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
595 for pl := range loadedPlugins {
596 _, err := Call(pl+".onRune", string(e.Rune()), v)
597 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
603 curMacro = append(curMacro, e.Rune())
606 v.SetCursor(&v.Buf.Cursor)
609 case *tcell.EventPaste:
610 // Check viewtype if readonly don't paste (readonly help and log view etc.)
611 if v.Type.Readonly == false {
612 if !PreActionCall("Paste", v) {
616 for _, c := range v.Buf.cursors {
620 v.SetCursor(&v.Buf.Cursor)
622 PostActionCall("Paste", v)
624 case *tcell.EventMouse:
625 // Don't relocate for mouse events
628 button := e.Buttons()
630 for key, actions := range bindings {
631 if button == key.buttons && e.Modifiers() == key.modifiers {
632 for _, c := range v.Buf.cursors {
637 relocate = v.ExecuteActions(actions) || relocate
639 v.SetCursor(&v.Buf.Cursor)
644 for key, actions := range mouseBindings {
645 if button == key.buttons && e.Modifiers() == key.modifiers {
646 for _, action := range actions {
653 case tcell.ButtonNone:
654 // Mouse event with no click
655 if !v.mouseReleased {
656 // Mouse was just released
659 x -= v.lineNumOffset - v.leftCol + v.x
662 // Relocating here isn't really necessary because the cursor will
663 // be in the right place from the last mouse event
664 // However, if we are running in a terminal that doesn't support mouse motion
665 // events, this still allows the user to make selections, except only after they
668 if !v.doubleClick && !v.tripleClick {
669 v.MoveToMouseClick(x, y)
670 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
671 v.Cursor.CopySelection("primary")
673 v.mouseReleased = true
680 // We run relocate again because there's a bug with relocating with softwrap
681 // when for example you jump to the bottom of the buffer and it tries to
682 // calculate where to put the topline so that the bottom line is at the bottom
683 // of the terminal and it runs into problems with visual lines vs real lines.
684 // This is (hopefully) a temporary solution
689 func (v *View) mainCursor() bool {
690 return v.Buf.curCursor == len(v.Buf.cursors)-1
693 // GutterMessage creates a message in this view's gutter
694 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
696 gutterMsg := GutterMessage{
701 for _, v := range v.messages {
702 for _, gmsg := range v {
703 if gmsg.lineNum == lineN {
708 messages := v.messages[section]
709 v.messages[section] = append(messages, gutterMsg)
712 // ClearGutterMessages clears all gutter messages from a given section
713 func (v *View) ClearGutterMessages(section string) {
714 v.messages[section] = []GutterMessage{}
717 // ClearAllGutterMessages clears all the gutter messages
718 func (v *View) ClearAllGutterMessages() {
719 for k := range v.messages {
720 v.messages[k] = []GutterMessage{}
724 // Opens the given help page in a new horizontal split
725 func (v *View) openHelp(helpPage string) {
726 if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
727 TermMessage("Unable to load help text", helpPage, "\n", err)
729 helpBuffer := NewBufferFromString(string(data), helpPage+".md")
730 helpBuffer.name = "Help"
732 if v.Type == vtHelp {
733 v.OpenBuffer(helpBuffer)
736 CurView().Type = vtHelp
741 // DisplayView draws the view to the screen
742 func (v *View) DisplayView() {
743 if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
747 if v.Type == vtLog || v.Type == vtRaw {
748 // Log or raw views should always follow the cursor...
752 // We need to know the string length of the largest line number
753 // so we can pad appropriately when displaying line numbers
754 maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))
756 if v.Buf.Settings["ruler"] == true {
757 // + 1 for the little space after the line number
758 v.lineNumOffset = maxLineNumLength + 1
763 // We need to add to the line offset if there are gutter messages
764 var hasGutterMessages bool
765 for _, v := range v.messages {
767 hasGutterMessages = true
770 if hasGutterMessages {
776 // One space for the extra split divider
781 xOffset := v.x + v.lineNumOffset
789 v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)
795 for visualLineN, line = range v.cellview.lines {
802 if firstChar != nil {
803 if firstChar.realLoc.Y == realLineN {
806 realLineN = firstChar.realLoc.Y
811 colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
812 if colorcolumn != 0 {
813 style := GetColor("color-column")
814 fg, _, _ := style.Decompose()
815 st := defStyle.Background(fg)
816 screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
821 // If there are gutter messages we need to display the '>>' symbol here
822 if hasGutterMessages {
823 // msgOnLine stores whether or not there is a gutter message on this line in particular
825 for k := range v.messages {
826 for _, msg := range v.messages[k] {
827 if msg.lineNum == realLineN {
829 gutterStyle := defStyle
832 if style, ok := colorscheme["gutter-info"]; ok {
836 if style, ok := colorscheme["gutter-warning"]; ok {
840 if style, ok := colorscheme["gutter-error"]; ok {
844 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
846 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
848 if v.Cursor.Y == realLineN && !messenger.hasPrompt {
849 messenger.Message(msg.msg)
850 messenger.gutterMessage = true
855 // If there is no message on this line we just display an empty offset
857 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
859 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
861 if v.Cursor.Y == realLineN && messenger.gutterMessage {
863 messenger.gutterMessage = false
868 lineNumStyle := defStyle
869 if v.Buf.Settings["ruler"] == true {
870 // Write the line number
871 if style, ok := colorscheme["line-number"]; ok {
874 if style, ok := colorscheme["current-line-number"]; ok {
875 if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
880 lineNum := strconv.Itoa(realLineN + 1)
882 // Write the spaces before the line number if necessary
883 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
884 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
887 if softwrapped && visualLineN != 0 {
888 // Pad without the line number because it was written on the visual line before
890 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
894 // Write the actual line number
895 for _, ch := range lineNum {
896 screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle)
901 // Write the extra space
902 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
908 for _, char := range line {
910 lineStyle := char.style
912 colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
913 if colorcolumn != 0 && char.visualLoc.X == colorcolumn {
914 style := GetColor("color-column")
915 fg, _, _ := style.Decompose()
916 lineStyle = lineStyle.Background(fg)
919 charLoc := char.realLoc
920 for _, c := range v.Buf.cursors {
922 if v.Cursor.HasSelection() &&
923 (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
924 charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
925 // The current character is selected
926 lineStyle = defStyle.Reverse(true)
928 if style, ok := colorscheme["selection"]; ok {
933 v.SetCursor(&v.Buf.Cursor)
935 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
936 !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
937 style := GetColor("cursor-line")
938 fg, _, _ := style.Decompose()
939 lineStyle = lineStyle.Background(fg)
942 screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
944 for i, c := range v.Buf.cursors {
946 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
947 v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) {
948 ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
952 v.SetCursor(&v.Buf.Cursor)
963 lastX = xOffset + lastChar.visualLoc.X + lastChar.width
964 for i, c := range v.Buf.cursors {
966 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
967 v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
968 ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
969 cx, cy = lastX, yOffset+lastChar.visualLoc.Y
972 v.SetCursor(&v.Buf.Cursor)
973 realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
974 visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
975 } else if len(line) == 0 {
976 for i, c := range v.Buf.cursors {
978 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
979 v.Cursor.Y == realLineN {
980 ShowMultiCursor(xOffset, yOffset+visualLineN, i)
981 cx, cy = xOffset, yOffset+visualLineN
984 v.SetCursor(&v.Buf.Cursor)
986 realLoc = Loc{0, realLineN}
987 visualLoc = Loc{0, visualLineN}
990 if v.Cursor.HasSelection() &&
991 (realLoc.GreaterEqual(v.Cursor.CurSelection[0]) && realLoc.LessThan(v.Cursor.CurSelection[1]) ||
992 realLoc.LessThan(v.Cursor.CurSelection[0]) && realLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
993 // The current character is selected
994 selectStyle := defStyle.Reverse(true)
996 if style, ok := colorscheme["selection"]; ok {
999 screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle)
1002 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
1003 !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
1004 for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
1005 style := GetColor("cursor-line")
1006 fg, _, _ := style.Decompose()
1007 style = style.Background(fg)
1008 if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) {
1009 screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
1016 dividerStyle := defStyle
1017 if style, ok := colorscheme["divider"]; ok {
1018 dividerStyle = style
1020 for i := 0; i < v.Height; i++ {
1021 screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true))
1026 // ShowMultiCursor will display a cursor at a location
1027 // If i == 0 then the terminal cursor will be used
1028 // Otherwise a fake cursor will be drawn at the position
1029 func ShowMultiCursor(x, y, i int) {
1031 screen.ShowCursor(x, y)
1033 r, _, _, _ := screen.GetContent(x, y)
1034 screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
1038 // Display renders the view, the cursor, and statusline
1039 func (v *View) Display() {
1040 if globalSettings["termtitle"].(bool) {
1041 screen.SetTitle("micro: " + v.Buf.GetName())
1044 // Don't draw the cursor if it is out of the viewport or if it has a selection
1045 if v.Num == tabs[curTab].CurView && (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1 || v.Cursor.HasSelection()) {
1048 _, screenH := screen.Size()
1050 if v.Buf.Settings["scrollbar"].(bool) {
1051 v.scrollbar.Display()
1054 if v.Buf.Settings["statusline"].(bool) {
1056 } else if (v.y + v.Height) != screenH-1 {
1057 for x := 0; x < v.Width; x++ {
1058 screen.SetContent(v.x+x, v.y+v.Height, '-', nil, defStyle.Reverse(true))