]> git.lizzy.rs Git - micro.git/blob - cmd/micro/view.go
af40fb95a4ad6d4063188297d39aa31132a23b7e
[micro.git] / cmd / micro / view.go
1 package main
2
3 import (
4         "reflect"
5         "runtime"
6         "strconv"
7         "strings"
8         "time"
9
10         "github.com/mattn/go-runewidth"
11         "github.com/yuin/gopher-lua"
12         "github.com/zyedidia/tcell"
13 )
14
15 // The View struct stores information about a view into a buffer.
16 // It stores information about the cursor, and the viewport
17 // that the user sees the buffer from.
18 type View struct {
19         // A pointer to the buffer's cursor for ease of access
20         Cursor *Cursor
21
22         // The topmost line, used for vertical scrolling
23         Topline int
24         // The leftmost column, used for horizontal scrolling
25         leftCol int
26
27         // Percentage of the terminal window that this view takes up (from 0 to 100)
28         widthPercent  int
29         heightPercent int
30
31         // Specifies whether or not this view holds a help buffer
32         Help bool
33
34         // Actual with and height
35         width  int
36         height int
37
38         // Where this view is located
39         x, y int
40
41         // How much to offset because of line numbers
42         lineNumOffset int
43
44         // Holds the list of gutter messages
45         messages map[string][]GutterMessage
46
47         // This is the index of this view in the views array
48         Num int
49         // What tab is this view stored in
50         TabNum int
51
52         // The buffer
53         Buf *Buffer
54         // The statusline
55         sline Statusline
56
57         // Since tcell doesn't differentiate between a mouse release event
58         // and a mouse move event with no keys pressed, we need to keep
59         // track of whether or not the mouse was pressed (or not released) last event to determine
60         // mouse release events
61         mouseReleased bool
62
63         // This stores when the last click was
64         // This is useful for detecting double and triple clicks
65         lastClickTime time.Time
66
67         // lastCutTime stores when the last ctrl+k was issued.
68         // It is used for clearing the clipboard to replace it with fresh cut lines.
69         lastCutTime time.Time
70
71         // freshClip returns true if the clipboard has never been pasted.
72         freshClip bool
73
74         // Was the last mouse event actually a double click?
75         // Useful for detecting triple clicks -- if a double click is detected
76         // but the last mouse event was actually a double click, it's a triple click
77         doubleClick bool
78         // Same here, just to keep track for mouse move events
79         tripleClick bool
80
81         // Syntax highlighting matches
82         matches SyntaxMatches
83         // The matches from the last frame
84         lastMatches SyntaxMatches
85
86         splitNode *LeafNode
87 }
88
89 // NewView returns a new fullscreen view
90 func NewView(buf *Buffer) *View {
91         screenW, screenH := screen.Size()
92         return NewViewWidthHeight(buf, screenW, screenH-1)
93 }
94
95 // NewViewWidthHeight returns a new view with the specified width and height percentages
96 // Note that w and h are percentages not actual values
97 func NewViewWidthHeight(buf *Buffer, w, h int) *View {
98         v := new(View)
99
100         v.x, v.y = 0, 0
101
102         v.width = w
103         v.height = h
104
105         v.ToggleTabbar()
106
107         v.OpenBuffer(buf)
108
109         v.messages = make(map[string][]GutterMessage)
110
111         v.sline = Statusline{
112                 view: v,
113         }
114
115         if settings["statusline"].(bool) {
116                 v.height--
117         }
118
119         return v
120 }
121
122 func (v *View) ToggleStatusLine() {
123         if settings["statusline"].(bool) {
124                 v.height--
125         } else {
126                 v.height++
127         }
128 }
129
130 func (v *View) ToggleTabbar() {
131         if len(tabs) > 1 {
132                 if v.y == 0 {
133                         // Include one line for the tab bar at the top
134                         v.height--
135                         v.y = 1
136                 }
137         } else {
138                 if v.y == 1 {
139                         v.y = 0
140                         v.height++
141                 }
142         }
143 }
144
145 // ScrollUp scrolls the view up n lines (if possible)
146 func (v *View) ScrollUp(n int) {
147         // Try to scroll by n but if it would overflow, scroll by 1
148         if v.Topline-n >= 0 {
149                 v.Topline -= n
150         } else if v.Topline > 0 {
151                 v.Topline--
152         }
153 }
154
155 // ScrollDown scrolls the view down n lines (if possible)
156 func (v *View) ScrollDown(n int) {
157         // Try to scroll by n but if it would overflow, scroll by 1
158         if v.Topline+n <= v.Buf.NumLines-v.height {
159                 v.Topline += n
160         } else if v.Topline < v.Buf.NumLines-v.height {
161                 v.Topline++
162         }
163 }
164
165 // CanClose returns whether or not the view can be closed
166 // If there are unsaved changes, the user will be asked if the view can be closed
167 // causing them to lose the unsaved changes
168 // The message is what to print after saying "You have unsaved changes. "
169 func (v *View) CanClose(msg string) bool {
170         if v.Buf.IsModified {
171                 quit, canceled := messenger.Prompt("You have unsaved changes. "+msg, "Unsaved", NoCompletion)
172                 if !canceled {
173                         if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" {
174                                 return true
175                         } else if strings.ToLower(quit) == "save" || strings.ToLower(quit) == "s" {
176                                 v.Save()
177                                 return true
178                         }
179                 }
180         } else {
181                 return true
182         }
183         return false
184 }
185
186 // OpenBuffer opens a new buffer in this view.
187 // This resets the topline, event handler and cursor.
188 func (v *View) OpenBuffer(buf *Buffer) {
189         screen.Clear()
190         v.CloseBuffer()
191         v.Buf = buf
192         v.Cursor = &buf.Cursor
193         v.Topline = 0
194         v.leftCol = 0
195         v.Cursor.ResetSelection()
196         v.Relocate()
197         v.messages = make(map[string][]GutterMessage)
198
199         v.matches = Match(v)
200
201         // Set mouseReleased to true because we assume the mouse is not being pressed when
202         // the editor is opened
203         v.mouseReleased = true
204         v.lastClickTime = time.Time{}
205 }
206
207 // CloseBuffer performs any closing functions on the buffer
208 func (v *View) CloseBuffer() {
209         if v.Buf != nil {
210                 v.Buf.Serialize()
211         }
212 }
213
214 // ReOpen reloads the current buffer
215 func (v *View) ReOpen() {
216         if v.CanClose("Continue? (yes, no, save) ") {
217                 screen.Clear()
218                 v.Buf.ReOpen()
219                 v.Relocate()
220                 v.matches = Match(v)
221         }
222 }
223
224 // HSplit opens a horizontal split with the given buffer
225 func (v *View) HSplit(buf *Buffer) bool {
226         v.splitNode.HSplit(buf)
227         tabs[v.TabNum].Resize()
228         return false
229 }
230
231 // VSplit opens a vertical split with the given buffer
232 func (v *View) VSplit(buf *Buffer) bool {
233         v.splitNode.VSplit(buf)
234         tabs[v.TabNum].Resize()
235         return false
236 }
237
238 // Relocate moves the view window so that the cursor is in view
239 // This is useful if the user has scrolled far away, and then starts typing
240 func (v *View) Relocate() bool {
241         ret := false
242         cy := v.Cursor.Y
243         scrollmargin := int(settings["scrollmargin"].(float64))
244         if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
245                 v.Topline = cy - scrollmargin
246                 ret = true
247         } else if cy < v.Topline {
248                 v.Topline = cy
249                 ret = true
250         }
251         if cy > v.Topline+v.height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
252                 v.Topline = cy - v.height + 1 + scrollmargin
253                 ret = true
254         } else if cy >= v.Buf.NumLines-scrollmargin && cy > v.height {
255                 v.Topline = v.Buf.NumLines - v.height
256                 ret = true
257         }
258
259         cx := v.Cursor.GetVisualX()
260         if cx < v.leftCol {
261                 v.leftCol = cx
262                 ret = true
263         }
264         if cx+v.lineNumOffset+1 > v.leftCol+v.width {
265                 v.leftCol = cx - v.width + v.lineNumOffset + 1
266                 ret = true
267         }
268         return ret
269 }
270
271 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
272 // by a mouse click
273 func (v *View) MoveToMouseClick(x, y int) {
274         if y-v.Topline > v.height-1 {
275                 v.ScrollDown(1)
276                 y = v.height + v.Topline - 1
277         }
278         if y >= v.Buf.NumLines {
279                 y = v.Buf.NumLines - 1
280         }
281         if y < 0 {
282                 y = 0
283         }
284         if x < 0 {
285                 x = 0
286         }
287
288         x = v.Cursor.GetCharPosInLine(y, x)
289         if x > Count(v.Buf.Line(y)) {
290                 x = Count(v.Buf.Line(y))
291         }
292         v.Cursor.X = x
293         v.Cursor.Y = y
294         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
295 }
296
297 // HandleEvent handles an event passed by the main loop
298 func (v *View) HandleEvent(event tcell.Event) {
299         // This bool determines whether the view is relocated at the end of the function
300         // By default it's true because most events should cause a relocate
301         relocate := true
302
303         v.Buf.CheckModTime()
304
305         switch e := event.(type) {
306         case *tcell.EventResize:
307                 // Window resized
308                 tabs[v.TabNum].Resize()
309         case *tcell.EventKey:
310                 if e.Key() == tcell.KeyRune && (e.Modifiers() == 0 || e.Modifiers() == tcell.ModShift) {
311                         // Insert a character
312                         if v.Cursor.HasSelection() {
313                                 v.Cursor.DeleteSelection()
314                                 v.Cursor.ResetSelection()
315                         }
316                         v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
317                         v.Cursor.Right()
318
319                         for _, pl := range loadedPlugins {
320                                 _, err := Call(pl+".onRune", []string{string(e.Rune())})
321                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
322                                         TermMessage(err)
323                                 }
324                         }
325                 } else {
326                         for key, actions := range bindings {
327                                 if e.Key() == key.keyCode {
328                                         if e.Key() == tcell.KeyRune {
329                                                 if e.Rune() != key.r {
330                                                         continue
331                                                 }
332                                         }
333                                         if e.Modifiers() == key.modifiers {
334                                                 relocate = false
335                                                 for _, action := range actions {
336                                                         executeAction := true
337                                                         funcName := strings.Split(runtime.FuncForPC(reflect.ValueOf(action).Pointer()).Name(), ".")
338                                                         for _, pl := range loadedPlugins {
339                                                                 ret, err := Call(pl+".pre"+funcName[len(funcName)-1], nil)
340                                                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
341                                                                         TermMessage(err)
342                                                                         continue
343                                                                 }
344                                                                 if ret == lua.LFalse {
345                                                                         executeAction = false
346                                                                 }
347                                                         }
348                                                         if executeAction {
349                                                                 relocate = action(v) || relocate
350                                                                 for _, pl := range loadedPlugins {
351                                                                         _, err := Call(pl+".on"+funcName[len(funcName)-1], nil)
352                                                                         if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
353                                                                                 TermMessage(err)
354                                                                                 continue
355                                                                         }
356                                                                 }
357                                                         }
358                                                 }
359                                         }
360                                 }
361                         }
362                 }
363         case *tcell.EventPaste:
364                 if v.Cursor.HasSelection() {
365                         v.Cursor.DeleteSelection()
366                         v.Cursor.ResetSelection()
367                 }
368                 clip := e.Text()
369                 v.Buf.Insert(v.Cursor.Loc, clip)
370                 v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
371                 v.freshClip = false
372         case *tcell.EventMouse:
373                 x, y := e.Position()
374                 x -= v.lineNumOffset - v.leftCol + v.x
375                 y += v.Topline - v.y
376                 // Don't relocate for mouse events
377                 relocate = false
378
379                 button := e.Buttons()
380
381                 switch button {
382                 case tcell.Button1:
383                         // Left click
384                         if v.mouseReleased {
385                                 v.MoveToMouseClick(x, y)
386                                 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
387                                         if v.doubleClick {
388                                                 // Triple click
389                                                 v.lastClickTime = time.Now()
390
391                                                 v.tripleClick = true
392                                                 v.doubleClick = false
393
394                                                 v.Cursor.SelectLine()
395                                         } else {
396                                                 // Double click
397                                                 v.lastClickTime = time.Now()
398
399                                                 v.doubleClick = true
400                                                 v.tripleClick = false
401
402                                                 v.Cursor.SelectWord()
403                                         }
404                                 } else {
405                                         v.doubleClick = false
406                                         v.tripleClick = false
407                                         v.lastClickTime = time.Now()
408
409                                         v.Cursor.OrigSelection[0] = v.Cursor.Loc
410                                         v.Cursor.CurSelection[0] = v.Cursor.Loc
411                                         v.Cursor.CurSelection[1] = v.Cursor.Loc
412                                 }
413                                 v.mouseReleased = false
414                         } else if !v.mouseReleased {
415                                 v.MoveToMouseClick(x, y)
416                                 if v.tripleClick {
417                                         v.Cursor.AddLineToSelection()
418                                 } else if v.doubleClick {
419                                         v.Cursor.AddWordToSelection()
420                                 } else {
421                                         v.Cursor.CurSelection[1] = v.Cursor.Loc
422                                 }
423                         }
424                 case tcell.ButtonNone:
425                         // Mouse event with no click
426                         if !v.mouseReleased {
427                                 // Mouse was just released
428
429                                 // Relocating here isn't really necessary because the cursor will
430                                 // be in the right place from the last mouse event
431                                 // However, if we are running in a terminal that doesn't support mouse motion
432                                 // events, this still allows the user to make selections, except only after they
433                                 // release the mouse
434
435                                 if !v.doubleClick && !v.tripleClick {
436                                         v.MoveToMouseClick(x, y)
437                                         v.Cursor.CurSelection[1] = v.Cursor.Loc
438                                 }
439                                 v.mouseReleased = true
440                         }
441                 case tcell.WheelUp:
442                         // Scroll up
443                         scrollspeed := int(settings["scrollspeed"].(float64))
444                         v.ScrollUp(scrollspeed)
445                 case tcell.WheelDown:
446                         // Scroll down
447                         scrollspeed := int(settings["scrollspeed"].(float64))
448                         v.ScrollDown(scrollspeed)
449                 }
450         }
451
452         if relocate {
453                 v.Relocate()
454         }
455         if settings["syntax"].(bool) {
456                 v.matches = Match(v)
457         }
458 }
459
460 // GutterMessage creates a message in this view's gutter
461 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
462         lineN--
463         gutterMsg := GutterMessage{
464                 lineNum: lineN,
465                 msg:     msg,
466                 kind:    kind,
467         }
468         for _, v := range v.messages {
469                 for _, gmsg := range v {
470                         if gmsg.lineNum == lineN {
471                                 return
472                         }
473                 }
474         }
475         messages := v.messages[section]
476         v.messages[section] = append(messages, gutterMsg)
477 }
478
479 // ClearGutterMessages clears all gutter messages from a given section
480 func (v *View) ClearGutterMessages(section string) {
481         v.messages[section] = []GutterMessage{}
482 }
483
484 // ClearAllGutterMessages clears all the gutter messages
485 func (v *View) ClearAllGutterMessages() {
486         for k := range v.messages {
487                 v.messages[k] = []GutterMessage{}
488         }
489 }
490
491 func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) {
492         if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
493                 screen.SetContent(x, y, ch, combc, style)
494         }
495 }
496
497 // DisplayView renders the view to the screen
498 func (v *View) DisplayView() {
499         // The charNum we are currently displaying
500         // starts at the start of the viewport
501         charNum := Loc{0, v.Topline}
502
503         // Convert the length of buffer to a string, and get the length of the string
504         // We are going to have to offset by that amount
505         maxLineLength := len(strconv.Itoa(v.Buf.NumLines))
506
507         if settings["ruler"] == true {
508                 // + 1 for the little space after the line number
509                 v.lineNumOffset = maxLineLength + 1
510         } else {
511                 v.lineNumOffset = 0
512         }
513
514         // We need to add to the line offset if there are gutter messages
515         var hasGutterMessages bool
516         for _, v := range v.messages {
517                 if len(v) > 0 {
518                         hasGutterMessages = true
519                 }
520         }
521         if hasGutterMessages {
522                 v.lineNumOffset += 2
523         }
524
525         if v.x != 0 {
526                 // One space for the extra split divider
527                 v.lineNumOffset++
528         }
529
530         // These represent the current screen coordinates
531         screenX, screenY := 0, 0
532
533         highlightStyle := defStyle
534
535         // ViewLine is the current line from the top of the viewport
536         for viewLine := 0; viewLine < v.height; viewLine++ {
537                 screenY = v.y + viewLine
538                 screenX = v.x
539
540                 // This is the current line number of the buffer that we are drawing
541                 curLineN := viewLine + v.Topline
542
543                 if v.x != 0 {
544                         // Draw the split divider
545                         v.drawCell(screenX, screenY, '|', nil, defStyle.Reverse(true))
546                         screenX++
547                 }
548
549                 // If the buffer is smaller than the view height we have to clear all this space
550                 if curLineN >= v.Buf.NumLines {
551                         for i := screenX; i < v.x+v.width; i++ {
552                                 v.drawCell(i, screenY, ' ', nil, defStyle)
553                         }
554
555                         continue
556                 }
557                 line := v.Buf.Line(curLineN)
558
559                 // If there are gutter messages we need to display the '>>' symbol here
560                 if hasGutterMessages {
561                         // msgOnLine stores whether or not there is a gutter message on this line in particular
562                         msgOnLine := false
563                         for k := range v.messages {
564                                 for _, msg := range v.messages[k] {
565                                         if msg.lineNum == curLineN {
566                                                 msgOnLine = true
567                                                 gutterStyle := defStyle
568                                                 switch msg.kind {
569                                                 case GutterInfo:
570                                                         if style, ok := colorscheme["gutter-info"]; ok {
571                                                                 gutterStyle = style
572                                                         }
573                                                 case GutterWarning:
574                                                         if style, ok := colorscheme["gutter-warning"]; ok {
575                                                                 gutterStyle = style
576                                                         }
577                                                 case GutterError:
578                                                         if style, ok := colorscheme["gutter-error"]; ok {
579                                                                 gutterStyle = style
580                                                         }
581                                                 }
582                                                 v.drawCell(screenX, screenY, '>', nil, gutterStyle)
583                                                 screenX++
584                                                 v.drawCell(screenX, screenY, '>', nil, gutterStyle)
585                                                 screenX++
586                                                 if v.Cursor.Y == curLineN {
587                                                         messenger.Message(msg.msg)
588                                                         messenger.gutterMessage = true
589                                                 }
590                                         }
591                                 }
592                         }
593                         // If there is no message on this line we just display an empty offset
594                         if !msgOnLine {
595                                 v.drawCell(screenX, screenY, ' ', nil, defStyle)
596                                 screenX++
597                                 v.drawCell(screenX, screenY, ' ', nil, defStyle)
598                                 screenX++
599                                 if v.Cursor.Y == curLineN && messenger.gutterMessage {
600                                         messenger.Reset()
601                                         messenger.gutterMessage = false
602                                 }
603                         }
604                 }
605
606                 if settings["ruler"] == true {
607                         // Write the line number
608                         lineNumStyle := defStyle
609                         if style, ok := colorscheme["line-number"]; ok {
610                                 lineNumStyle = style
611                         }
612
613                         lineNum := strconv.Itoa(curLineN + 1)
614
615                         // Write the spaces before the line number if necessary
616                         for i := 0; i < maxLineLength-len(lineNum); i++ {
617                                 v.drawCell(screenX, screenY, ' ', nil, lineNumStyle)
618                                 screenX++
619                         }
620                         // Write the actual line number
621                         for _, ch := range lineNum {
622                                 v.drawCell(screenX, screenY, ch, nil, lineNumStyle)
623                                 screenX++
624                         }
625
626                         // Write the extra space
627                         v.drawCell(screenX, screenY, ' ', nil, lineNumStyle)
628                         screenX++
629                 }
630
631                 // Now we actually draw the line
632                 for colN, ch := range line {
633                         lineStyle := defStyle
634
635                         if settings["syntax"].(bool) {
636                                 // Syntax highlighting is enabled
637                                 highlightStyle = v.matches[viewLine][colN]
638                         }
639
640                         if v.Cursor.HasSelection() &&
641                                 (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
642                                         charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
643                                 // The current character is selected
644                                 lineStyle = defStyle.Reverse(true)
645
646                                 if style, ok := colorscheme["selection"]; ok {
647                                         lineStyle = style
648                                 }
649                         } else {
650                                 lineStyle = highlightStyle
651                         }
652
653                         // We need to display the background of the linestyle with the correct color if cursorline is enabled
654                         // and this is the current view and there is no selection on this line and the cursor is on this line
655                         if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
656                                 if style, ok := colorscheme["cursor-line"]; ok {
657                                         fg, _, _ := style.Decompose()
658                                         lineStyle = lineStyle.Background(fg)
659                                 }
660                         }
661
662                         if ch == '\t' {
663                                 // If the character we are displaying is a tab, we need to do a bunch of special things
664
665                                 // First the user may have configured an `indent-char` to be displayed to show that this
666                                 // is a tab character
667                                 lineIndentStyle := defStyle
668                                 if style, ok := colorscheme["indent-char"]; ok {
669                                         lineIndentStyle = style
670                                 }
671                                 if v.Cursor.HasSelection() &&
672                                         (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
673                                                 charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
674
675                                         lineIndentStyle = defStyle.Reverse(true)
676
677                                         if style, ok := colorscheme["selection"]; ok {
678                                                 lineIndentStyle = style
679                                         }
680                                 }
681                                 if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
682                                         if style, ok := colorscheme["cursor-line"]; ok {
683                                                 fg, _, _ := style.Decompose()
684                                                 lineIndentStyle = lineIndentStyle.Background(fg)
685                                         }
686                                 }
687                                 // Here we get the indent char
688                                 indentChar := []rune(settings["indentchar"].(string))
689                                 if screenX-v.x-v.leftCol >= v.lineNumOffset {
690                                         v.drawCell(screenX-v.leftCol, screenY, indentChar[0], nil, lineIndentStyle)
691                                 }
692                                 // Now the tab has to be displayed as a bunch of spaces
693                                 tabSize := int(settings["tabsize"].(float64))
694                                 for i := 0; i < tabSize-1; i++ {
695                                         screenX++
696                                         if screenX-v.x-v.leftCol >= v.lineNumOffset {
697                                                 v.drawCell(screenX-v.leftCol, screenY, ' ', nil, lineStyle)
698                                         }
699                                 }
700                         } else if runewidth.RuneWidth(ch) > 1 {
701                                 if screenX-v.x-v.leftCol >= v.lineNumOffset {
702                                         v.drawCell(screenX, screenY, ch, nil, lineStyle)
703                                 }
704                                 for i := 0; i < runewidth.RuneWidth(ch)-1; i++ {
705                                         screenX++
706                                         if screenX-v.x-v.leftCol >= v.lineNumOffset {
707                                                 v.drawCell(screenX-v.leftCol, screenY, '<', nil, lineStyle)
708                                         }
709                                 }
710                         } else {
711                                 if screenX-v.x-v.leftCol >= v.lineNumOffset {
712                                         v.drawCell(screenX-v.leftCol, screenY, ch, nil, lineStyle)
713                                 }
714                         }
715                         charNum = charNum.Move(1, v.Buf)
716                         screenX++
717                 }
718                 // Here we are at a newline
719
720                 // The newline may be selected, in which case we should draw the selection style
721                 // with a space to represent it
722                 if v.Cursor.HasSelection() &&
723                         (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
724                                 charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
725
726                         selectStyle := defStyle.Reverse(true)
727
728                         if style, ok := colorscheme["selection"]; ok {
729                                 selectStyle = style
730                         }
731                         v.drawCell(screenX, screenY, ' ', nil, selectStyle)
732                         screenX++
733                 }
734
735                 charNum = charNum.Move(1, v.Buf)
736
737                 for i := 0; i < v.width; i++ {
738                         lineStyle := defStyle
739                         if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
740                                 if style, ok := colorscheme["cursor-line"]; ok {
741                                         fg, _, _ := style.Decompose()
742                                         lineStyle = lineStyle.Background(fg)
743                                 }
744                         }
745                         if screenX-v.x-v.leftCol+i >= v.lineNumOffset {
746                                 v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle)
747                         }
748                 }
749         }
750 }
751
752 // DisplayCursor draws the current buffer's cursor to the screen
753 func (v *View) DisplayCursor() {
754         // Don't draw the cursor if it is out of the viewport or if it has a selection
755         if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.height-1) || v.Cursor.HasSelection() {
756                 screen.HideCursor()
757         } else {
758                 screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, v.Cursor.Y-v.Topline+v.y)
759         }
760 }
761
762 // Display renders the view, the cursor, and statusline
763 func (v *View) Display() {
764         v.DisplayView()
765         if v.Num == tabs[curTab].curView {
766                 v.DisplayCursor()
767         }
768         _, screenH := screen.Size()
769         if settings["statusline"].(bool) {
770                 v.sline.Display()
771         } else if (v.y + v.height) != screenH-1 {
772                 for x := 0; x < v.width; x++ {
773                         screen.SetContent(v.x+x, v.y+v.height, '-', nil, defStyle.Reverse(true))
774                 }
775         }
776 }