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}
27 vtTerm = ViewType{5, true, true}
30 // The View struct stores information about a view into a buffer.
31 // It stores information about the cursor, and the viewport
32 // that the user sees the buffer from.
34 // A pointer to the buffer's cursor for ease of access
37 // The topmost line, used for vertical scrolling
39 // The leftmost column, used for horizontal scrolling
42 // Specifies whether or not this view holds a help buffer
45 // Actual width and height
52 // Where this view is located
55 // How much to offset because of line numbers
58 // Holds the list of gutter messages
59 messages map[string][]GutterMessage
61 // This is the index of this view in the views array
63 // What tab is this view stored in
71 // Since tcell doesn't differentiate between a mouse release event
72 // and a mouse move event with no keys pressed, we need to keep
73 // track of whether or not the mouse was pressed (or not released) last event to determine
74 // mouse release events
77 // We need to keep track of insert key press toggle
79 // This stores when the last click was
80 // This is useful for detecting double and triple clicks
81 lastClickTime time.Time
84 // lastCutTime stores when the last ctrl+k was issued.
85 // It is used for clearing the clipboard to replace it with fresh cut lines.
88 // freshClip returns true if the clipboard has never been pasted.
91 // Was the last mouse event actually a double click?
92 // Useful for detecting triple clicks -- if a double click is detected
93 // but the last mouse event was actually a double click, it's a triple click
95 // Same here, just to keep track for mouse move events
98 // The cellview used for displaying and syntax highlighting
110 // NewView returns a new fullscreen view
111 func NewView(buf *Buffer) *View {
112 screenW, screenH := screen.Size()
113 return NewViewWidthHeight(buf, screenW, screenH)
116 // NewViewWidthHeight returns a new view with the specified width and height
117 // Note that w and h are raw column and row values
118 func NewViewWidthHeight(buf *Buffer, w, h int) *View {
125 v.cellview = new(CellView)
131 v.messages = make(map[string][]GutterMessage)
133 v.sline = &Statusline{
137 v.scrollbar = &ScrollBar{
141 if v.Buf.Settings["statusline"].(bool) {
145 v.term = new(Terminal)
147 for pl := range loadedPlugins {
148 _, err := Call(pl+".onViewOpen", v)
149 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
158 // ToggleStatusLine creates an extra row for the statusline if necessary
159 func (v *View) ToggleStatusLine() {
160 if v.Buf.Settings["statusline"].(bool) {
167 // StartTerminal execs a command in this view
168 func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
169 err := v.term.Start(execCmd, v, getOutput)
171 v.term.callback = luaCallback
173 v.term.Resize(v.Width, v.Height)
179 // CloseTerminal shuts down the tty running in this view
180 // and returns it to the default view type
181 func (v *View) CloseTerminal() {
185 // ToggleTabbar creates an extra row for the tabbar if necessary
186 func (v *View) ToggleTabbar() {
189 // Include one line for the tab bar at the top
201 func (v *View) paste(clip string) {
202 leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
204 if v.Cursor.HasSelection() {
205 v.Cursor.DeleteSelection()
206 v.Cursor.ResetSelection()
208 clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
209 v.Buf.Insert(v.Cursor.Loc, clip)
210 // v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
212 messenger.Message("Pasted clipboard")
215 // ScrollUp scrolls the view up n lines (if possible)
216 func (v *View) ScrollUp(n int) {
217 // Try to scroll by n but if it would overflow, scroll by 1
218 if v.Topline-n >= 0 {
220 } else if v.Topline > 0 {
225 // ScrollDown scrolls the view down n lines (if possible)
226 func (v *View) ScrollDown(n int) {
227 // Try to scroll by n but if it would overflow, scroll by 1
228 if v.Topline+n <= v.Buf.NumLines {
230 } else if v.Topline < v.Buf.NumLines-1 {
235 // CanClose returns whether or not the view can be closed
236 // If there are unsaved changes, the user will be asked if the view can be closed
237 // causing them to lose the unsaved changes
238 func (v *View) CanClose() bool {
239 if v.Type == vtDefault && v.Buf.Modified() {
242 if v.Buf.Settings["autosave"].(bool) {
245 choice, canceled = messenger.YesNoPrompt("Save changes to " + v.Buf.GetName() + " before closing? (y,n,esc) ")
259 // OpenBuffer opens a new buffer in this view.
260 // This resets the topline, event handler and cursor.
261 func (v *View) OpenBuffer(buf *Buffer) {
265 v.Cursor = &buf.Cursor
268 v.Cursor.ResetSelection()
271 v.messages = make(map[string][]GutterMessage)
273 // Set mouseReleased to true because we assume the mouse is not being pressed when
274 // the editor is opened
275 v.mouseReleased = true
276 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
278 v.isOverwriteMode = false
279 v.lastClickTime = time.Time{}
281 GlobalPluginCall("onBufferOpen", v.Buf)
282 GlobalPluginCall("onViewOpen", v)
285 // Open opens the given file in the view
286 func (v *View) Open(filename string) {
287 filename = ReplaceHome(filename)
288 file, err := os.Open(filename)
289 fileInfo, _ := os.Stat(filename)
291 if err == nil && fileInfo.IsDir() {
292 messenger.Error(filename, " is a directory")
300 messenger.Message(err.Error())
301 // File does not exist -- create an empty buffer with that name
302 buf = NewBufferFromString("", filename)
304 buf = NewBuffer(file, FSize(file), filename)
309 // CloseBuffer performs any closing functions on the buffer
310 func (v *View) CloseBuffer() {
316 // ReOpen reloads the current buffer
317 func (v *View) ReOpen() {
325 // HSplit opens a horizontal split with the given buffer
326 func (v *View) HSplit(buf *Buffer) {
328 if v.Buf.Settings["splitbottom"].(bool) {
331 v.splitNode.HSplit(buf, v.Num+i)
334 // VSplit opens a vertical split with the given buffer
335 func (v *View) VSplit(buf *Buffer) {
337 if v.Buf.Settings["splitright"].(bool) {
340 v.splitNode.VSplit(buf, v.Num+i)
343 // HSplitIndex opens a horizontal split with the given buffer at the given index
344 func (v *View) HSplitIndex(buf *Buffer, splitIndex int) {
345 v.splitNode.HSplit(buf, splitIndex)
348 // VSplitIndex opens a vertical split with the given buffer at the given index
349 func (v *View) VSplitIndex(buf *Buffer, splitIndex int) {
350 v.splitNode.VSplit(buf, splitIndex)
353 // GetSoftWrapLocation gets the location of a visual click on the screen and converts it to col,line
354 func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
355 if !v.Buf.Settings["softwrap"].(bool) {
356 if vy >= v.Buf.NumLines {
357 vy = v.Buf.NumLines - 1
359 vx = v.Cursor.GetCharPosInLine(vy, vx)
363 screenX, screenY := 0, v.Topline
364 for lineN := v.Topline; lineN < v.Bottomline(); lineN++ {
365 line := v.Buf.Line(lineN)
366 if lineN >= v.Buf.NumLines {
367 return 0, v.Buf.NumLines - 1
371 for _, ch := range line {
372 if screenX >= v.Width-v.lineNumOffset {
377 if screenX == vx && screenY == vy {
382 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
398 // Bottomline returns the line number of the lowest line in the view
399 // You might think that this is obviously just v.Topline + v.Height
400 // but if softwrap is enabled things get complicated since one buffer
401 // line can take up multiple lines in the view
402 func (v *View) Bottomline() int {
403 if !v.Buf.Settings["softwrap"].(bool) {
404 return v.Topline + v.Height
407 screenX, screenY := 0, 0
409 for lineN := v.Topline; lineN < v.Topline+v.Height; lineN++ {
410 line := v.Buf.Line(lineN)
413 for _, ch := range line {
414 if screenX >= v.Width-v.lineNumOffset {
420 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
430 if screenY >= v.Height {
434 return numLines + v.Topline
437 // Relocate moves the view window so that the cursor is in view
438 // This is useful if the user has scrolled far away, and then starts typing
439 func (v *View) Relocate() bool {
440 height := v.Bottomline() - v.Topline
443 scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64))
444 if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
445 v.Topline = cy - scrollmargin
447 } else if cy < v.Topline {
451 if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
452 v.Topline = cy - height + 1 + scrollmargin
454 } else if cy >= v.Buf.NumLines-scrollmargin && cy >= height {
455 v.Topline = v.Buf.NumLines - height
459 if !v.Buf.Settings["softwrap"].(bool) {
460 cx := v.Cursor.GetVisualX()
465 if cx+v.lineNumOffset+1 > v.leftCol+v.Width {
466 v.leftCol = cx - v.Width + v.lineNumOffset + 1
473 // GetMouseClickLocation gets the location in the buffer from a mouse click
475 func (v *View) GetMouseClickLocation(x, y int) (int, int) {
476 x -= v.lineNumOffset - v.leftCol + v.x
479 if y-v.Topline > v.Height-1 {
481 y = v.Height + v.Topline - 1
490 newX, newY := v.GetSoftWrapLocation(x, y)
491 if newX > Count(v.Buf.Line(newY)) {
492 newX = Count(v.Buf.Line(newY))
498 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
500 func (v *View) MoveToMouseClick(x, y int) {
501 if y-v.Topline > v.Height-1 {
503 y = v.Height + v.Topline - 1
512 x, y = v.GetSoftWrapLocation(x, y)
513 if x > Count(v.Buf.Line(y)) {
514 x = Count(v.Buf.Line(y))
518 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
521 // Execute actions executes the supplied actions
522 func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
524 readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
525 for _, action := range actions {
526 readonlyBindingsResult := false
527 funcName := ShortFuncName(action)
528 if v.Type.Readonly == true {
529 // check for readonly and if true only let key bindings get called if they do not change the contents.
530 for _, readonlyBindings := range readonlyBindingsList {
531 if strings.Contains(funcName, readonlyBindings) {
532 readonlyBindingsResult = true
536 if !readonlyBindingsResult {
537 // call the key binding
538 relocate = action(v, true) || relocate
540 if funcName != "ToggleMacro" && funcName != "PlayMacro" {
542 curMacro = append(curMacro, action)
551 // SetCursor sets the view's and buffer's cursor
552 func (v *View) SetCursor(c *Cursor) bool {
557 v.Buf.curCursor = c.Num
562 // HandleEvent handles an event passed by the main loop
563 func (v *View) HandleEvent(event tcell.Event) {
564 if v.Type == vtTerm {
565 v.term.HandleEvent(event)
570 v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
571 v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
573 switch e := event.(type) {
574 case *tcell.EventKey:
575 if e.Key() == tcell.KeyCtrlQ {
583 // This bool determines whether the view is relocated at the end of the function
584 // By default it's true because most events should cause a relocate
589 switch e := event.(type) {
590 case *tcell.EventRaw:
591 for key, actions := range bindings {
592 if key.keyCode == -1 {
593 if e.EscSeq() == key.escape {
594 for _, c := range v.Buf.cursors {
600 relocate = v.ExecuteActions(actions) || relocate
602 v.SetCursor(&v.Buf.Cursor)
608 case *tcell.EventKey:
609 // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
611 for key, actions := range bindings {
612 if e.Key() == key.keyCode {
613 if e.Key() == tcell.KeyRune {
614 if e.Rune() != key.r {
618 if e.Modifiers() == key.modifiers {
619 for _, c := range v.Buf.cursors {
626 relocate = v.ExecuteActions(actions) || relocate
628 v.SetCursor(&v.Buf.Cursor)
635 if !isBinding && e.Key() == tcell.KeyRune {
636 // Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
637 if v.Type.Readonly == false {
638 for _, c := range v.Buf.cursors {
641 // Insert a character
642 if v.Cursor.HasSelection() {
643 v.Cursor.DeleteSelection()
644 v.Cursor.ResetSelection()
647 if v.isOverwriteMode {
650 v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
652 v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
655 for pl := range loadedPlugins {
656 _, err := Call(pl+".onRune", string(e.Rune()), v)
657 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
663 curMacro = append(curMacro, e.Rune())
666 v.SetCursor(&v.Buf.Cursor)
669 case *tcell.EventPaste:
670 // Check viewtype if readonly don't paste (readonly help and log view etc.)
671 if v.Type.Readonly == false {
672 if !PreActionCall("Paste", v) {
676 for _, c := range v.Buf.cursors {
680 v.SetCursor(&v.Buf.Cursor)
682 PostActionCall("Paste", v)
684 case *tcell.EventMouse:
685 // Don't relocate for mouse events
688 button := e.Buttons()
690 for key, actions := range bindings {
691 if button == key.buttons && e.Modifiers() == key.modifiers {
692 for _, c := range v.Buf.cursors {
697 relocate = v.ExecuteActions(actions) || relocate
699 v.SetCursor(&v.Buf.Cursor)
704 for key, actions := range mouseBindings {
705 if button == key.buttons && e.Modifiers() == key.modifiers {
706 for _, action := range actions {
713 case tcell.ButtonNone:
714 // Mouse event with no click
715 if !v.mouseReleased {
716 // Mouse was just released
719 x -= v.lineNumOffset - v.leftCol + v.x
722 // Relocating here isn't really necessary because the cursor will
723 // be in the right place from the last mouse event
724 // However, if we are running in a terminal that doesn't support mouse motion
725 // events, this still allows the user to make selections, except only after they
728 if !v.doubleClick && !v.tripleClick {
729 v.MoveToMouseClick(x, y)
730 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
731 v.Cursor.CopySelection("primary")
733 v.mouseReleased = true
740 // We run relocate again because there's a bug with relocating with softwrap
741 // when for example you jump to the bottom of the buffer and it tries to
742 // calculate where to put the topline so that the bottom line is at the bottom
743 // of the terminal and it runs into problems with visual lines vs real lines.
744 // This is (hopefully) a temporary solution
749 func (v *View) mainCursor() bool {
750 return v.Buf.curCursor == len(v.Buf.cursors)-1
753 // GutterMessage creates a message in this view's gutter
754 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
756 gutterMsg := GutterMessage{
761 for _, v := range v.messages {
762 for _, gmsg := range v {
763 if gmsg.lineNum == lineN {
768 messages := v.messages[section]
769 v.messages[section] = append(messages, gutterMsg)
772 // ClearGutterMessages clears all gutter messages from a given section
773 func (v *View) ClearGutterMessages(section string) {
774 v.messages[section] = []GutterMessage{}
777 // ClearAllGutterMessages clears all the gutter messages
778 func (v *View) ClearAllGutterMessages() {
779 for k := range v.messages {
780 v.messages[k] = []GutterMessage{}
784 // Opens the given help page in a new horizontal split
785 func (v *View) openHelp(helpPage string) {
786 if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
787 TermMessage("Unable to load help text", helpPage, "\n", err)
789 helpBuffer := NewBufferFromString(string(data), helpPage+".md")
790 helpBuffer.name = "Help"
792 if v.Type == vtHelp {
793 v.OpenBuffer(helpBuffer)
796 CurView().Type = vtHelp
801 // DisplayView draws the view to the screen
802 func (v *View) DisplayView() {
803 if v.Type == vtTerm {
808 if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
812 if v.Type == vtLog || v.Type == vtRaw {
813 // Log or raw views should always follow the cursor...
817 // We need to know the string length of the largest line number
818 // so we can pad appropriately when displaying line numbers
819 maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))
821 if v.Buf.Settings["ruler"] == true {
822 // + 1 for the little space after the line number
823 v.lineNumOffset = maxLineNumLength + 1
828 // We need to add to the line offset if there are gutter messages
829 var hasGutterMessages bool
830 for _, v := range v.messages {
832 hasGutterMessages = true
835 if hasGutterMessages {
841 // One space for the extra split divider
846 xOffset := v.x + v.lineNumOffset
854 v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)
860 for visualLineN, line = range v.cellview.lines {
867 if firstChar != nil {
868 if firstChar.realLoc.Y == realLineN {
871 realLineN = firstChar.realLoc.Y
876 colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
877 if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
878 style := GetColor("color-column")
879 fg, _, _ := style.Decompose()
880 st := defStyle.Background(fg)
881 screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
886 // If there are gutter messages we need to display the '>>' symbol here
887 if hasGutterMessages {
888 // msgOnLine stores whether or not there is a gutter message on this line in particular
890 for k := range v.messages {
891 for _, msg := range v.messages[k] {
892 if msg.lineNum == realLineN {
894 gutterStyle := defStyle
897 if style, ok := colorscheme["gutter-info"]; ok {
901 if style, ok := colorscheme["gutter-warning"]; ok {
905 if style, ok := colorscheme["gutter-error"]; ok {
909 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
911 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
913 if v.Cursor.Y == realLineN && !messenger.hasPrompt {
914 messenger.Message(msg.msg)
915 messenger.gutterMessage = true
920 // If there is no message on this line we just display an empty offset
922 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
924 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
926 if v.Cursor.Y == realLineN && messenger.gutterMessage {
928 messenger.gutterMessage = false
933 lineNumStyle := defStyle
934 if v.Buf.Settings["ruler"] == true {
935 // Write the line number
936 if style, ok := colorscheme["line-number"]; ok {
939 if style, ok := colorscheme["current-line-number"]; ok {
940 if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
945 lineNum := strconv.Itoa(realLineN + 1)
947 // Write the spaces before the line number if necessary
948 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
949 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
952 if softwrapped && visualLineN != 0 {
953 // Pad without the line number because it was written on the visual line before
955 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
959 // Write the actual line number
960 for _, ch := range lineNum {
961 screen.SetContent(screenX+divider, yOffset+visualLineN, ch, nil, lineNumStyle)
966 // Write the extra space
967 screen.SetContent(screenX+divider, yOffset+visualLineN, ' ', nil, lineNumStyle)
973 for _, char := range line {
975 lineStyle := char.style
977 colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
978 if colorcolumn != 0 && char.visualLoc.X == colorcolumn {
979 style := GetColor("color-column")
980 fg, _, _ := style.Decompose()
981 lineStyle = lineStyle.Background(fg)
984 charLoc := char.realLoc
985 for _, c := range v.Buf.cursors {
987 if v.Cursor.HasSelection() &&
988 (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
989 charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
990 // The current character is selected
991 lineStyle = defStyle.Reverse(true)
993 if style, ok := colorscheme["selection"]; ok {
998 v.SetCursor(&v.Buf.Cursor)
1000 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
1001 !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
1002 style := GetColor("cursor-line")
1003 fg, _, _ := style.Decompose()
1004 lineStyle = lineStyle.Background(fg)
1007 screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
1009 for i, c := range v.Buf.cursors {
1011 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
1012 v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) {
1013 ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
1017 v.SetCursor(&v.Buf.Cursor)
1027 if lastChar != nil {
1028 lastX = xOffset + lastChar.visualLoc.X + lastChar.width
1029 for i, c := range v.Buf.cursors {
1031 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
1032 v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
1033 ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
1034 cx, cy = lastX, yOffset+lastChar.visualLoc.Y
1037 v.SetCursor(&v.Buf.Cursor)
1038 realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
1039 visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
1040 } else if len(line) == 0 {
1041 for i, c := range v.Buf.cursors {
1043 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
1044 v.Cursor.Y == realLineN {
1045 ShowMultiCursor(xOffset, yOffset+visualLineN, i)
1046 cx, cy = xOffset, yOffset+visualLineN
1049 v.SetCursor(&v.Buf.Cursor)
1051 realLoc = Loc{0, realLineN}
1052 visualLoc = Loc{0, visualLineN}
1055 if v.Cursor.HasSelection() &&
1056 (realLoc.GreaterEqual(v.Cursor.CurSelection[0]) && realLoc.LessThan(v.Cursor.CurSelection[1]) ||
1057 realLoc.LessThan(v.Cursor.CurSelection[0]) && realLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
1058 // The current character is selected
1059 selectStyle := defStyle.Reverse(true)
1061 if style, ok := colorscheme["selection"]; ok {
1064 screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle)
1067 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
1068 !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
1069 for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
1070 style := GetColor("cursor-line")
1071 fg, _, _ := style.Decompose()
1072 style = style.Background(fg)
1073 if !(tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && i == cx && yOffset+visualLineN == cy) {
1074 screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
1081 dividerStyle := defStyle
1082 if style, ok := colorscheme["divider"]; ok {
1083 dividerStyle = style
1085 for i := 0; i < v.Height; i++ {
1086 screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true))
1091 // ShowMultiCursor will display a cursor at a location
1092 // If i == 0 then the terminal cursor will be used
1093 // Otherwise a fake cursor will be drawn at the position
1094 func ShowMultiCursor(x, y, i int) {
1096 screen.ShowCursor(x, y)
1098 r, _, _, _ := screen.GetContent(x, y)
1099 screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
1103 // Display renders the view, the cursor, and statusline
1104 func (v *View) Display() {
1105 if globalSettings["termtitle"].(bool) {
1106 screen.SetTitle("micro: " + v.Buf.GetName())
1109 // Don't draw the cursor if it is out of the viewport or if it has a selection
1110 if v.Num == tabs[curTab].CurView && (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1 || v.Cursor.HasSelection()) {
1113 _, screenH := screen.Size()
1115 if v.Buf.Settings["scrollbar"].(bool) {
1116 v.scrollbar.Display()
1119 if v.Buf.Settings["statusline"].(bool) {
1121 } else if (v.y + v.Height) != screenH-1 {
1122 for x := 0; x < v.Width; x++ {
1123 screen.SetContent(v.x+x, v.y+v.Height, '-', nil, defStyle.Reverse(true))