]> git.lizzy.rs Git - micro.git/blob - cmd/micro/view.go
722f42b044e13a40e4deda19270ff2d530dd24ff
[micro.git] / cmd / micro / view.go
1 package main
2
3 import (
4         "os"
5         "strconv"
6         "strings"
7         "time"
8
9         "github.com/mitchellh/go-homedir"
10         "github.com/zyedidia/tcell"
11 )
12
13 type ViewType struct {
14         readonly bool // The file cannot be edited
15         scratch  bool // The file cannot be saved
16 }
17
18 var (
19         vtDefault = ViewType{false, false}
20         vtHelp    = ViewType{true, true}
21         vtLog     = ViewType{true, true}
22         vtScratch = ViewType{false, true}
23 )
24
25 type scrollInfo struct {
26         left   int
27         offset int
28         style  *tcell.Style
29 }
30
31 // The View struct stores information about a view into a buffer.
32 // It stores information about the cursor, and the viewport
33 // that the user sees the buffer from.
34 type View struct {
35         // A pointer to the buffer's cursor for ease of access
36         Cursor *Cursor
37
38         // The topmost line, used for vertical scrolling
39         Topline int
40         // The leftmost column, used for horizontal scrolling
41         leftCol     int
42         hScrollInfo []*scrollInfo
43
44         // Specifies whether or not this view holds a help buffer
45         Type ViewType
46
47         // Actual width and height
48         Width  int
49         Height int
50
51         LockWidth  bool
52         LockHeight bool
53
54         // Where this view is located
55         x, y int
56
57         // How much to offset because of line numbers
58         lineNumOffset int
59
60         // Holds the list of gutter messages
61         messages map[string][]GutterMessage
62
63         // This is the index of this view in the views array
64         Num int
65         // What tab is this view stored in
66         TabNum int
67
68         // The buffer
69         Buf *Buffer
70         // The statusline
71         sline Statusline
72
73         // Since tcell doesn't differentiate between a mouse release event
74         // and a mouse move event with no keys pressed, we need to keep
75         // track of whether or not the mouse was pressed (or not released) last event to determine
76         // mouse release events
77         mouseReleased bool
78
79         // This stores when the last click was
80         // This is useful for detecting double and triple clicks
81         lastClickTime time.Time
82
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.
85         lastCutTime time.Time
86
87         // freshClip returns true if the clipboard has never been pasted.
88         freshClip bool
89
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
93         doubleClick bool
94         // Same here, just to keep track for mouse move events
95         tripleClick bool
96
97         cellview *CellView
98
99         splitNode *LeafNode
100 }
101
102 // NewView returns a new fullscreen view
103 func NewView(buf *Buffer) *View {
104         screenW, screenH := screen.Size()
105         return NewViewWidthHeight(buf, screenW, screenH)
106 }
107
108 // NewViewWidthHeight returns a new view with the specified width and height
109 // Note that w and h are raw column and row values
110 func NewViewWidthHeight(buf *Buffer, w, h int) *View {
111         v := new(View)
112
113         v.x, v.y = 0, 0
114
115         v.Width = w
116         v.Height = h
117         v.cellview = new(CellView)
118
119         v.ToggleTabbar()
120
121         v.OpenBuffer(buf)
122
123         v.messages = make(map[string][]GutterMessage)
124
125         v.sline = Statusline{
126                 view: v,
127         }
128
129         if v.Buf.Settings["statusline"].(bool) {
130                 v.Height--
131         }
132
133         for pl := range loadedPlugins {
134                 _, err := Call(pl+".onViewOpen", v)
135                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
136                         TermMessage(err)
137                         continue
138                 }
139         }
140
141         return v
142 }
143
144 // ToggleStatusLine creates an extra row for the statusline if necessary
145 func (v *View) ToggleStatusLine() {
146         if v.Buf.Settings["statusline"].(bool) {
147                 v.Height--
148         } else {
149                 v.Height++
150         }
151 }
152
153 // ToggleTabbar creates an extra row for the tabbar if necessary
154 func (v *View) ToggleTabbar() {
155         if len(tabs) > 1 {
156                 if v.y == 0 {
157                         // Include one line for the tab bar at the top
158                         v.Height--
159                         v.y = 1
160                 }
161         } else {
162                 if v.y == 1 {
163                         v.y = 0
164                         v.Height++
165                 }
166         }
167 }
168
169 func (v *View) calcHScrollInfo() {
170         v.hScrollInfo = make([]*scrollInfo, v.Height)
171         for i := v.Topline; i < v.Topline+v.Height; i++ {
172                 if i >= v.Buf.NumLines {
173                         break
174                 }
175
176                 left, offset, style := visualToCharPos(v.leftCol, i, v.Buf.Line(i), v.Buf, int(v.Buf.Settings["tabsize"].(float64)))
177                 v.hScrollInfo[i-v.Topline] = &scrollInfo{left, offset, style}
178         }
179 }
180
181 func (v *View) SetLeftCol(n int) {
182         v.leftCol = n
183         v.calcHScrollInfo()
184 }
185
186 func (v *View) paste(clip string) {
187         leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
188
189         if v.Cursor.HasSelection() {
190                 v.Cursor.DeleteSelection()
191                 v.Cursor.ResetSelection()
192         }
193         clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
194         v.Buf.Insert(v.Cursor.Loc, clip)
195         v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
196         v.freshClip = false
197         messenger.Message("Pasted clipboard")
198 }
199
200 // ScrollUp scrolls the view up n lines (if possible)
201 func (v *View) ScrollUp(n int) {
202         // Try to scroll by n but if it would overflow, scroll by 1
203         if v.Topline-n >= 0 {
204                 v.Topline -= n
205         } else if v.Topline > 0 {
206                 v.Topline--
207         }
208 }
209
210 // ScrollDown scrolls the view down n lines (if possible)
211 func (v *View) ScrollDown(n int) {
212         // Try to scroll by n but if it would overflow, scroll by 1
213         if v.Topline+n <= v.Buf.NumLines {
214                 v.Topline += n
215         } else if v.Topline < v.Buf.NumLines-1 {
216                 v.Topline++
217         }
218 }
219
220 // CanClose returns whether or not the view can be closed
221 // If there are unsaved changes, the user will be asked if the view can be closed
222 // causing them to lose the unsaved changes
223 func (v *View) CanClose() bool {
224         if v.Type == vtDefault && v.Buf.IsModified {
225                 var char rune
226                 var canceled bool
227                 if v.Buf.Settings["autosave"].(bool) {
228                         char = 'y'
229                 } else {
230                         char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.GetName()+" before closing? (y,n,esc) ", 'y', 'n')
231                 }
232                 if !canceled {
233                         if char == 'y' {
234                                 v.Save(true)
235                                 return true
236                         } else if char == 'n' {
237                                 return true
238                         }
239                 }
240         } else {
241                 return true
242         }
243         return false
244 }
245
246 // OpenBuffer opens a new buffer in this view.
247 // This resets the topline, event handler and cursor.
248 func (v *View) OpenBuffer(buf *Buffer) {
249         screen.Clear()
250         v.CloseBuffer()
251         v.Buf = buf
252         v.Cursor = &buf.Cursor
253         v.Topline = 0
254         // v.leftCol = 0
255         v.SetLeftCol(0)
256         v.Cursor.ResetSelection()
257         v.Relocate()
258         v.Center(false)
259         v.messages = make(map[string][]GutterMessage)
260
261         // Set mouseReleased to true because we assume the mouse is not being pressed when
262         // the editor is opened
263         v.mouseReleased = true
264         v.lastClickTime = time.Time{}
265 }
266
267 // Open opens the given file in the view
268 func (v *View) Open(filename string) {
269         home, _ := homedir.Dir()
270         filename = strings.Replace(filename, "~", home, 1)
271         file, err := os.Open(filename)
272         defer file.Close()
273
274         var buf *Buffer
275         if err != nil {
276                 messenger.Message(err.Error())
277                 // File does not exist -- create an empty buffer with that name
278                 buf = NewBuffer(strings.NewReader(""), filename)
279         } else {
280                 buf = NewBuffer(file, filename)
281         }
282         v.OpenBuffer(buf)
283 }
284
285 // CloseBuffer performs any closing functions on the buffer
286 func (v *View) CloseBuffer() {
287         if v.Buf != nil {
288                 v.Buf.Serialize()
289         }
290 }
291
292 // ReOpen reloads the current buffer
293 func (v *View) ReOpen() {
294         if v.CanClose() {
295                 screen.Clear()
296                 v.Buf.ReOpen()
297                 v.Relocate()
298         }
299 }
300
301 // HSplit opens a horizontal split with the given buffer
302 func (v *View) HSplit(buf *Buffer) {
303         i := 0
304         if v.Buf.Settings["splitBottom"].(bool) {
305                 i = 1
306         }
307         v.splitNode.HSplit(buf, v.Num+i)
308 }
309
310 // VSplit opens a vertical split with the given buffer
311 func (v *View) VSplit(buf *Buffer) {
312         i := 0
313         if v.Buf.Settings["splitRight"].(bool) {
314                 i = 1
315         }
316         v.splitNode.VSplit(buf, v.Num+i)
317 }
318
319 // HSplitIndex opens a horizontal split with the given buffer at the given index
320 func (v *View) HSplitIndex(buf *Buffer, splitIndex int) {
321         v.splitNode.HSplit(buf, splitIndex)
322 }
323
324 // VSplitIndex opens a vertical split with the given buffer at the given index
325 func (v *View) VSplitIndex(buf *Buffer, splitIndex int) {
326         v.splitNode.VSplit(buf, splitIndex)
327 }
328
329 // GetSoftWrapLocation gets the location of a visual click on the screen and converts it to col,line
330 func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
331         if !v.Buf.Settings["softwrap"].(bool) {
332                 if vy >= v.Buf.NumLines {
333                         vy = v.Buf.NumLines - 1
334                 }
335                 vx = v.Cursor.GetCharPosInLine(vy, vx)
336                 return vx, vy
337         }
338
339         screenX, screenY := 0, v.Topline
340         for lineN := v.Topline; lineN < v.Bottomline(); lineN++ {
341                 line := v.Buf.Line(lineN)
342                 if lineN >= v.Buf.NumLines {
343                         return 0, v.Buf.NumLines - 1
344                 }
345
346                 colN := 0
347                 for _, ch := range line {
348                         if screenX >= v.Width-v.lineNumOffset {
349                                 screenX = 0
350                                 screenY++
351                         }
352
353                         if screenX == vx && screenY == vy {
354                                 return colN, lineN
355                         }
356
357                         if ch == '\t' {
358                                 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
359                         }
360
361                         screenX++
362                         colN++
363                 }
364                 if screenY == vy {
365                         return colN, lineN
366                 }
367                 screenX = 0
368                 screenY++
369         }
370
371         return 0, 0
372 }
373
374 func (v *View) Bottomline() int {
375         if !v.Buf.Settings["softwrap"].(bool) {
376                 return v.Topline + v.Height
377         }
378
379         screenX, screenY := 0, 0
380         numLines := 0
381         for lineN := v.Topline; lineN < v.Topline+v.Height; lineN++ {
382                 line := v.Buf.Line(lineN)
383
384                 colN := 0
385                 for _, ch := range line {
386                         if screenX >= v.Width-v.lineNumOffset {
387                                 screenX = 0
388                                 screenY++
389                         }
390
391                         if ch == '\t' {
392                                 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
393                         }
394
395                         screenX++
396                         colN++
397                 }
398                 screenX = 0
399                 screenY++
400                 numLines++
401
402                 if screenY >= v.Height {
403                         break
404                 }
405         }
406         return numLines + v.Topline
407 }
408
409 // Relocate moves the view window so that the cursor is in view
410 // This is useful if the user has scrolled far away, and then starts typing
411 func (v *View) Relocate() bool {
412         height := v.Bottomline() - v.Topline
413         ret := false
414         cy := v.Cursor.Y
415         scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64))
416         if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
417                 v.Topline = cy - scrollmargin
418                 ret = true
419         } else if cy < v.Topline {
420                 v.Topline = cy
421                 ret = true
422         }
423         if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
424                 v.Topline = cy - height + 1 + scrollmargin
425                 ret = true
426         } else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
427                 v.Topline = v.Buf.NumLines - height
428                 ret = true
429         }
430
431         if !v.Buf.Settings["softwrap"].(bool) {
432                 cx := v.Cursor.GetVisualX()
433                 if cx < v.leftCol {
434                         // v.leftCol = cx
435                         v.SetLeftCol(cx)
436                         ret = true
437                 }
438                 if cx+v.lineNumOffset+1 > v.leftCol+v.Width {
439                         // v.leftCol = cx - v.Width + v.lineNumOffset + 1
440                         v.SetLeftCol(cx - v.Width + v.lineNumOffset + 1)
441                         ret = true
442                 }
443         }
444         return ret
445 }
446
447 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
448 // by a mouse click
449 func (v *View) MoveToMouseClick(x, y int) {
450         if y-v.Topline > v.Height-1 {
451                 v.ScrollDown(1)
452                 y = v.Height + v.Topline - 1
453         }
454         if y < 0 {
455                 y = 0
456         }
457         if x < 0 {
458                 x = 0
459         }
460
461         x, y = v.GetSoftWrapLocation(x, y)
462         // x = v.Cursor.GetCharPosInLine(y, x)
463         if x > Count(v.Buf.Line(y)) {
464                 x = Count(v.Buf.Line(y))
465         }
466         v.Cursor.X = x
467         v.Cursor.Y = y
468         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
469 }
470
471 // HandleEvent handles an event passed by the main loop
472 func (v *View) HandleEvent(event tcell.Event) {
473         // This bool determines whether the view is relocated at the end of the function
474         // By default it's true because most events should cause a relocate
475         relocate := true
476
477         v.Buf.CheckModTime()
478
479         switch e := event.(type) {
480         case *tcell.EventKey:
481                 // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
482                 isBinding := false
483                 if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
484                         for key, actions := range bindings {
485                                 if e.Key() == key.keyCode {
486                                         if e.Key() == tcell.KeyRune {
487                                                 if e.Rune() != key.r {
488                                                         continue
489                                                 }
490                                         }
491                                         if e.Modifiers() == key.modifiers {
492                                                 relocate = false
493                                                 isBinding = true
494                                                 for _, action := range actions {
495                                                         relocate = action(v, true) || relocate
496                                                         funcName := FuncName(action)
497                                                         if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" {
498                                                                 if recordingMacro {
499                                                                         curMacro = append(curMacro, action)
500                                                                 }
501                                                         }
502                                                 }
503                                                 break
504                                         }
505                                 }
506                         }
507                 }
508                 if !isBinding && e.Key() == tcell.KeyRune {
509                         // Insert a character
510                         if v.Cursor.HasSelection() {
511                                 v.Cursor.DeleteSelection()
512                                 v.Cursor.ResetSelection()
513                         }
514                         v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
515                         v.Cursor.Right()
516
517                         for pl := range loadedPlugins {
518                                 _, err := Call(pl+".onRune", string(e.Rune()), v)
519                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
520                                         TermMessage(err)
521                                 }
522                         }
523
524                         if recordingMacro {
525                                 curMacro = append(curMacro, e.Rune())
526                         }
527                 }
528         case *tcell.EventPaste:
529                 if !PreActionCall("Paste", v) {
530                         break
531                 }
532
533                 v.paste(e.Text())
534
535                 PostActionCall("Paste", v)
536         case *tcell.EventMouse:
537                 x, y := e.Position()
538                 x -= v.lineNumOffset - v.leftCol + v.x
539                 y += v.Topline - v.y
540                 // Don't relocate for mouse events
541                 relocate = false
542
543                 button := e.Buttons()
544
545                 switch button {
546                 case tcell.Button1:
547                         // Left click
548                         if v.mouseReleased {
549                                 v.MoveToMouseClick(x, y)
550                                 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
551                                         if v.doubleClick {
552                                                 // Triple click
553                                                 v.lastClickTime = time.Now()
554
555                                                 v.tripleClick = true
556                                                 v.doubleClick = false
557
558                                                 v.Cursor.SelectLine()
559                                                 v.Cursor.CopySelection("primary")
560                                         } else {
561                                                 // Double click
562                                                 v.lastClickTime = time.Now()
563
564                                                 v.doubleClick = true
565                                                 v.tripleClick = false
566
567                                                 v.Cursor.SelectWord()
568                                                 v.Cursor.CopySelection("primary")
569                                         }
570                                 } else {
571                                         v.doubleClick = false
572                                         v.tripleClick = false
573                                         v.lastClickTime = time.Now()
574
575                                         v.Cursor.OrigSelection[0] = v.Cursor.Loc
576                                         v.Cursor.CurSelection[0] = v.Cursor.Loc
577                                         v.Cursor.CurSelection[1] = v.Cursor.Loc
578                                 }
579                                 v.mouseReleased = false
580                         } else if !v.mouseReleased {
581                                 v.MoveToMouseClick(x, y)
582                                 if v.tripleClick {
583                                         v.Cursor.AddLineToSelection()
584                                 } else if v.doubleClick {
585                                         v.Cursor.AddWordToSelection()
586                                 } else {
587                                         v.Cursor.SetSelectionEnd(v.Cursor.Loc)
588                                         v.Cursor.CopySelection("primary")
589                                 }
590                         }
591                 case tcell.Button2:
592                         // Middle mouse button was clicked,
593                         // We should paste primary
594                         v.PastePrimary(true)
595                 case tcell.ButtonNone:
596                         // Mouse event with no click
597                         if !v.mouseReleased {
598                                 // Mouse was just released
599
600                                 // Relocating here isn't really necessary because the cursor will
601                                 // be in the right place from the last mouse event
602                                 // However, if we are running in a terminal that doesn't support mouse motion
603                                 // events, this still allows the user to make selections, except only after they
604                                 // release the mouse
605
606                                 if !v.doubleClick && !v.tripleClick {
607                                         v.MoveToMouseClick(x, y)
608                                         v.Cursor.SetSelectionEnd(v.Cursor.Loc)
609                                         v.Cursor.CopySelection("primary")
610                                 }
611                                 v.mouseReleased = true
612                         }
613                 case tcell.WheelUp:
614                         // Scroll up
615                         scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
616                         v.ScrollUp(scrollspeed)
617                 case tcell.WheelDown:
618                         // Scroll down
619                         scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
620                         v.ScrollDown(scrollspeed)
621                 }
622         }
623
624         if relocate {
625                 v.Relocate()
626         }
627 }
628
629 // GutterMessage creates a message in this view's gutter
630 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
631         lineN--
632         gutterMsg := GutterMessage{
633                 lineNum: lineN,
634                 msg:     msg,
635                 kind:    kind,
636         }
637         for _, v := range v.messages {
638                 for _, gmsg := range v {
639                         if gmsg.lineNum == lineN {
640                                 return
641                         }
642                 }
643         }
644         messages := v.messages[section]
645         v.messages[section] = append(messages, gutterMsg)
646 }
647
648 // ClearGutterMessages clears all gutter messages from a given section
649 func (v *View) ClearGutterMessages(section string) {
650         v.messages[section] = []GutterMessage{}
651 }
652
653 // ClearAllGutterMessages clears all the gutter messages
654 func (v *View) ClearAllGutterMessages() {
655         for k := range v.messages {
656                 v.messages[k] = []GutterMessage{}
657         }
658 }
659
660 // Opens the given help page in a new horizontal split
661 func (v *View) openHelp(helpPage string) {
662         if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
663                 TermMessage("Unable to load help text", helpPage, "\n", err)
664         } else {
665                 helpBuffer := NewBuffer(strings.NewReader(string(data)), helpPage+".md")
666                 helpBuffer.name = "Help"
667
668                 if v.Type == vtHelp {
669                         v.OpenBuffer(helpBuffer)
670                 } else {
671                         v.HSplit(helpBuffer)
672                         CurView().Type = vtHelp
673                 }
674         }
675 }
676
677 func (v *View) DisplayView() {
678         if v.Type == vtLog {
679                 // Log views should always follow the cursor...
680                 v.Relocate()
681         }
682
683         // We need to know the string length of the largest line number
684         // so we can pad appropriately when displaying line numbers
685         maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))
686
687         if v.Buf.Settings["ruler"] == true {
688                 // + 1 for the little space after the line number
689                 v.lineNumOffset = maxLineNumLength + 1
690         } else {
691                 v.lineNumOffset = 0
692         }
693
694         // We need to add to the line offset if there are gutter messages
695         var hasGutterMessages bool
696         for _, v := range v.messages {
697                 if len(v) > 0 {
698                         hasGutterMessages = true
699                 }
700         }
701         if hasGutterMessages {
702                 v.lineNumOffset += 2
703         }
704
705         if v.x != 0 {
706                 // One space for the extra split divider
707                 v.lineNumOffset++
708         }
709
710         xOffset := v.x + v.lineNumOffset
711         yOffset := v.y
712
713         height := v.Height
714         width := v.Width
715         left := v.leftCol
716         top := v.Topline
717
718         v.cellview.Draw(v.Buf, v.hScrollInfo, top, height, left, width-v.lineNumOffset)
719
720         screenX := v.x
721         realLineN := top - 1
722         visualLineN := 0
723         var line []*Char
724         for visualLineN, line = range v.cellview.lines {
725                 var firstChar *Char
726                 if len(line) > 0 {
727                         firstChar = line[0]
728                 }
729
730                 var softwrapped bool
731                 if firstChar != nil {
732                         if firstChar.realLoc.Y == realLineN {
733                                 softwrapped = true
734                         }
735                         realLineN = firstChar.realLoc.Y
736                 } else {
737                         realLineN++
738                 }
739
740                 screenX = v.x
741
742                 if v.x != 0 {
743                         // Draw the split divider
744                         screen.SetContent(screenX, yOffset+visualLineN, '|', nil, defStyle.Reverse(true))
745                         screenX++
746                 }
747
748                 // If there are gutter messages we need to display the '>>' symbol here
749                 if hasGutterMessages {
750                         // msgOnLine stores whether or not there is a gutter message on this line in particular
751                         msgOnLine := false
752                         for k := range v.messages {
753                                 for _, msg := range v.messages[k] {
754                                         if msg.lineNum == realLineN {
755                                                 msgOnLine = true
756                                                 gutterStyle := defStyle
757                                                 switch msg.kind {
758                                                 case GutterInfo:
759                                                         if style, ok := colorscheme["gutter-info"]; ok {
760                                                                 gutterStyle = style
761                                                         }
762                                                 case GutterWarning:
763                                                         if style, ok := colorscheme["gutter-warning"]; ok {
764                                                                 gutterStyle = style
765                                                         }
766                                                 case GutterError:
767                                                         if style, ok := colorscheme["gutter-error"]; ok {
768                                                                 gutterStyle = style
769                                                         }
770                                                 }
771                                                 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
772                                                 screenX++
773                                                 screen.SetContent(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
774                                                 screenX++
775                                                 if v.Cursor.Y == realLineN && !messenger.hasPrompt {
776                                                         messenger.Message(msg.msg)
777                                                         messenger.gutterMessage = true
778                                                 }
779                                         }
780                                 }
781                         }
782                         // If there is no message on this line we just display an empty offset
783                         if !msgOnLine {
784                                 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
785                                 screenX++
786                                 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, defStyle)
787                                 screenX++
788                                 if v.Cursor.Y == realLineN && messenger.gutterMessage {
789                                         messenger.Reset()
790                                         messenger.gutterMessage = false
791                                 }
792                         }
793                 }
794
795                 lineNumStyle := defStyle
796                 if v.Buf.Settings["ruler"] == true {
797                         // Write the line number
798                         if style, ok := colorscheme["line-number"]; ok {
799                                 lineNumStyle = style
800                         }
801                         if style, ok := colorscheme["current-line-number"]; ok {
802                                 if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
803                                         lineNumStyle = style
804                                 }
805                         }
806
807                         lineNum := strconv.Itoa(realLineN + 1)
808
809                         // Write the spaces before the line number if necessary
810                         for i := 0; i < maxLineNumLength-len(lineNum); i++ {
811                                 screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
812                                 screenX++
813                         }
814                         if softwrapped && visualLineN != 0 {
815                                 // Pad without the line number because it was written on the visual line before
816                                 for range lineNum {
817                                         screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
818                                         screenX++
819                                 }
820                         } else {
821                                 // Write the actual line number
822                                 for _, ch := range lineNum {
823                                         screen.SetContent(screenX, yOffset+visualLineN, ch, nil, lineNumStyle)
824                                         screenX++
825                                 }
826                         }
827
828                         // Write the extra space
829                         screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
830                         screenX++
831                 }
832
833                 var lastChar *Char
834                 cursorSet := false
835                 for _, char := range line {
836                         if char != nil {
837                                 lineStyle := char.style
838
839                                 charLoc := char.realLoc
840                                 if v.Cursor.HasSelection() &&
841                                         (charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
842                                                 charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
843                                         // The current character is selected
844                                         lineStyle = defStyle.Reverse(true)
845
846                                         if style, ok := colorscheme["selection"]; ok {
847                                                 lineStyle = style
848                                         }
849                                 }
850
851                                 if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
852                                         v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && !cursorSet {
853                                         screen.ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y)
854                                         cursorSet = true
855                                 }
856
857                                 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
858                                         !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
859                                         style := GetColor("cursor-line")
860                                         fg, _, _ := style.Decompose()
861                                         lineStyle = lineStyle.Background(fg)
862                                 }
863
864                                 screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
865
866                                 lastChar = char
867                         }
868                 }
869
870                 lastX := 0
871                 var realLoc Loc
872                 var visualLoc Loc
873                 if lastChar != nil {
874                         lastX = xOffset + lastChar.visualLoc.X + lastChar.width
875                         if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
876                                 v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
877                                 screen.ShowCursor(lastX, yOffset+lastChar.visualLoc.Y)
878                         }
879                         realLoc = Loc{lastChar.realLoc.X, realLineN}
880                         visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
881                 } else if len(line) == 0 {
882                         if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
883                                 v.Cursor.Y == realLineN {
884                                 screen.ShowCursor(xOffset, yOffset+visualLineN)
885                         }
886                         lastX = xOffset
887                         realLoc = Loc{0, realLineN}
888                         visualLoc = Loc{0, visualLineN}
889                 }
890
891                 if v.Cursor.HasSelection() &&
892                         (realLoc.GreaterEqual(v.Cursor.CurSelection[0]) && realLoc.LessThan(v.Cursor.CurSelection[1]) ||
893                                 realLoc.LessThan(v.Cursor.CurSelection[0]) && realLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
894                         // The current character is selected
895                         selectStyle := defStyle.Reverse(true)
896
897                         if style, ok := colorscheme["selection"]; ok {
898                                 selectStyle = style
899                         }
900                         screen.SetContent(xOffset+visualLoc.X, yOffset+visualLoc.Y, ' ', nil, selectStyle)
901                 }
902
903                 if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
904                         !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
905                         for i := lastX; i < xOffset+v.Width; i++ {
906                                 style := GetColor("cursor-line")
907                                 fg, _, _ := style.Decompose()
908                                 style = style.Background(fg)
909                                 screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
910                         }
911                 }
912         }
913
914         if v.x != 0 && visualLineN < v.Height {
915                 for i := visualLineN + 1; i < v.Height; i++ {
916                         screen.SetContent(v.x, yOffset+i, '|', nil, defStyle.Reverse(true))
917                 }
918         }
919 }
920
921 // DisplayCursor draws the current buffer's cursor to the screen
922 func (v *View) DisplayCursor(x, y int) {
923         // screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, y)
924         screen.ShowCursor(x, y)
925 }
926
927 // Display renders the view, the cursor, and statusline
928 func (v *View) Display() {
929         v.DisplayView()
930         // Don't draw the cursor if it is out of the viewport or if it has a selection
931         if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1) || v.Cursor.HasSelection() {
932                 screen.HideCursor()
933         }
934         _, screenH := screen.Size()
935         if v.Buf.Settings["statusline"].(bool) {
936                 v.sline.Display()
937         } else if (v.y + v.Height) != screenH-1 {
938                 for x := 0; x < v.Width; x++ {
939                         screen.SetContent(v.x+x, v.y+v.Height, '-', nil, defStyle.Reverse(true))
940                 }
941         }
942 }