]> git.lizzy.rs Git - micro.git/blob - cmd/micro/view.go
Change the help binding to Ctrl-g from Ctrl-h
[micro.git] / cmd / micro / view.go
1 package main
2
3 import (
4         "github.com/atotto/clipboard"
5         "github.com/gdamore/tcell"
6         "io/ioutil"
7         "strconv"
8         "strings"
9         "time"
10 )
11
12 // The View struct stores information about a view into a buffer.
13 // It has a stores information about the cursor, and the viewport
14 // that the user sees the buffer from.
15 type View struct {
16         cursor Cursor
17
18         // The topmost line, used for vertical scrolling
19         topline int
20         // The leftmost column, used for horizontal scrolling
21         leftCol int
22
23         // Percentage of the terminal window that this view takes up (from 0 to 100)
24         widthPercent  int
25         heightPercent int
26
27         // Actual with and height
28         width  int
29         height int
30
31         // How much to offset because of line numbers
32         lineNumOffset int
33
34         // The eventhandler for undo/redo
35         eh *EventHandler
36
37         // The buffer
38         buf *Buffer
39         // The statusline
40         sline Statusline
41
42         // Since tcell doesn't differentiate between a mouse release event
43         // and a mouse move event with no keys pressed, we need to keep
44         // track of whether or not the mouse was pressed (or not released) last event to determine
45         // mouse release events
46         mouseReleased bool
47
48         // This stores when the last click was
49         // This is useful for detecting double and triple clicks
50         lastClickTime time.Time
51
52         // Was the last mouse event actually a double click?
53         // Useful for detecting triple clicks -- if a double click is detected
54         // but the last mouse event was actually a double click, it's a triple click
55         doubleClick bool
56         // Same here, just to keep track for mouse move events
57         tripleClick bool
58
59         // Syntax highlighting matches
60         matches SyntaxMatches
61         // The matches from the last frame
62         lastMatches SyntaxMatches
63
64         // This is the range of lines that should have their syntax highlighting updated
65         updateLines [2]int
66 }
67
68 // NewView returns a new fullscreen view
69 func NewView(buf *Buffer) *View {
70         return NewViewWidthHeight(buf, 100, 100)
71 }
72
73 // NewViewWidthHeight returns a new view with the specified width and height percentages
74 // Note that w and h are percentages not actual values
75 func NewViewWidthHeight(buf *Buffer, w, h int) *View {
76         v := new(View)
77
78         v.buf = buf
79
80         v.widthPercent = w
81         v.heightPercent = h
82         v.Resize(screen.Size())
83
84         v.topline = 0
85         // Put the cursor at the first spot
86         v.cursor = Cursor{
87                 x: 0,
88                 y: 0,
89                 v: v,
90         }
91         v.cursor.ResetSelection()
92
93         v.eh = NewEventHandler(v)
94
95         v.sline = Statusline{
96                 view: v,
97         }
98
99         // Update the syntax highlighting for the entire buffer at the start
100         v.UpdateLines(v.topline, v.topline+v.height)
101         v.matches = Match(v)
102
103         // Set mouseReleased to true because we assume the mouse is not being pressed when
104         // the editor is opened
105         v.mouseReleased = true
106         v.lastClickTime = time.Time{}
107
108         return v
109 }
110
111 // UpdateLines sets the values for v.updateLines
112 func (v *View) UpdateLines(start, end int) {
113         v.updateLines[0] = start
114         v.updateLines[1] = end + 1
115 }
116
117 // Resize recalculates the actual width and height of the view from the width and height
118 // percentages
119 // This is usually called when the window is resized, or when a split has been added and
120 // the percentages have changed
121 func (v *View) Resize(w, h int) {
122         // Always include 1 line for the command line at the bottom
123         h--
124         v.width = int(float32(w) * float32(v.widthPercent) / 100)
125         // We subtract 1 for the statusline
126         v.height = int(float32(h)*float32(v.heightPercent)/100) - 1
127 }
128
129 // ScrollUp scrolls the view up n lines (if possible)
130 func (v *View) ScrollUp(n int) {
131         // Try to scroll by n but if it would overflow, scroll by 1
132         if v.topline-n >= 0 {
133                 v.topline -= n
134         } else if v.topline > 0 {
135                 v.topline--
136         }
137 }
138
139 // ScrollDown scrolls the view down n lines (if possible)
140 func (v *View) ScrollDown(n int) {
141         // Try to scroll by n but if it would overflow, scroll by 1
142         if v.topline+n <= len(v.buf.lines)-v.height {
143                 v.topline += n
144         } else if v.topline < len(v.buf.lines)-v.height {
145                 v.topline++
146         }
147 }
148
149 // PageUp scrolls the view up a page
150 func (v *View) PageUp() {
151         if v.topline > v.height {
152                 v.ScrollUp(v.height)
153         } else {
154                 v.topline = 0
155         }
156 }
157
158 // PageDown scrolls the view down a page
159 func (v *View) PageDown() {
160         if len(v.buf.lines)-(v.topline+v.height) > v.height {
161                 v.ScrollDown(v.height)
162         } else {
163                 if len(v.buf.lines) >= v.height {
164                         v.topline = len(v.buf.lines) - v.height
165                 }
166         }
167 }
168
169 // HalfPageUp scrolls the view up half a page
170 func (v *View) HalfPageUp() {
171         if v.topline > v.height/2 {
172                 v.ScrollUp(v.height / 2)
173         } else {
174                 v.topline = 0
175         }
176 }
177
178 // HalfPageDown scrolls the view down half a page
179 func (v *View) HalfPageDown() {
180         if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
181                 v.ScrollDown(v.height / 2)
182         } else {
183                 if len(v.buf.lines) >= v.height {
184                         v.topline = len(v.buf.lines) - v.height
185                 }
186         }
187 }
188
189 // CanClose returns whether or not the view can be closed
190 // If there are unsaved changes, the user will be asked if the view can be closed
191 // causing them to lose the unsaved changes
192 // The message is what to print after saying "You have unsaved changes. "
193 func (v *View) CanClose(msg string) bool {
194         if v.buf.IsDirty() {
195                 quit, canceled := messenger.Prompt("You have unsaved changes. " + msg)
196                 if !canceled {
197                         if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" {
198                                 return true
199                         }
200                 }
201         } else {
202                 return true
203         }
204         return false
205 }
206
207 // Save the buffer to disk
208 func (v *View) Save() {
209         // If this is an empty buffer, ask for a filename
210         if v.buf.path == "" {
211                 filename, canceled := messenger.Prompt("Filename: ")
212                 if !canceled {
213                         v.buf.path = filename
214                         v.buf.name = filename
215                 } else {
216                         return
217                 }
218         }
219         err := v.buf.Save()
220         if err != nil {
221                 messenger.Error(err.Error())
222         } else {
223                 messenger.Message("Saved " + v.buf.path)
224         }
225 }
226
227 // Copy the selection to the system clipboard
228 func (v *View) Copy() {
229         if v.cursor.HasSelection() {
230                 if !clipboard.Unsupported {
231                         clipboard.WriteAll(v.cursor.GetSelection())
232                 } else {
233                         messenger.Error("Clipboard is not supported on your system")
234                 }
235         }
236 }
237
238 // Cut the selection to the system clipboard
239 func (v *View) Cut() {
240         if v.cursor.HasSelection() {
241                 if !clipboard.Unsupported {
242                         clipboard.WriteAll(v.cursor.GetSelection())
243                         v.cursor.DeleteSelection()
244                         v.cursor.ResetSelection()
245                 } else {
246                         messenger.Error("Clipboard is not supported on your system")
247                 }
248         }
249 }
250
251 // Paste whatever is in the system clipboard into the buffer
252 // Delete and paste if the user has a selection
253 func (v *View) Paste() {
254         if !clipboard.Unsupported {
255                 if v.cursor.HasSelection() {
256                         v.cursor.DeleteSelection()
257                         v.cursor.ResetSelection()
258                 }
259                 clip, _ := clipboard.ReadAll()
260                 v.eh.Insert(v.cursor.Loc(), clip)
261                 v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
262         } else {
263                 messenger.Error("Clipboard is not supported on your system")
264         }
265 }
266
267 // SelectAll selects the entire buffer
268 func (v *View) SelectAll() {
269         v.cursor.curSelection[1] = 0
270         v.cursor.curSelection[0] = v.buf.Len()
271         // Put the cursor at the beginning
272         v.cursor.x = 0
273         v.cursor.y = 0
274 }
275
276 // OpenFile opens a new file in the current view
277 // It makes sure that the current buffer can be closed first (unsaved changes)
278 func (v *View) OpenFile() {
279         if v.CanClose("Continue? ") {
280                 filename, canceled := messenger.Prompt("File to open: ")
281                 if canceled {
282                         return
283                 }
284                 file, err := ioutil.ReadFile(filename)
285
286                 if err != nil {
287                         messenger.Error(err.Error())
288                         return
289                 }
290                 v.buf = NewBuffer(string(file), filename)
291         }
292 }
293
294 // Relocate moves the view window so that the cursor is in view
295 // This is useful if the user has scrolled far away, and then starts typing
296 func (v *View) Relocate() bool {
297         ret := false
298         cy := v.cursor.y
299         if cy < v.topline {
300                 v.topline = cy
301                 ret = true
302         }
303         if cy > v.topline+v.height-1 {
304                 v.topline = cy - v.height + 1
305                 ret = true
306         }
307
308         cx := v.cursor.GetVisualX()
309         if cx < v.leftCol {
310                 v.leftCol = cx
311                 ret = true
312         }
313         if cx+v.lineNumOffset+1 > v.leftCol+v.width {
314                 v.leftCol = cx - v.width + v.lineNumOffset + 1
315                 ret = true
316         }
317         return ret
318 }
319
320 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
321 // by a mouse click
322 func (v *View) MoveToMouseClick(x, y int) {
323         if y-v.topline > v.height-1 {
324                 v.ScrollDown(1)
325                 y = v.height + v.topline - 1
326         }
327         if y >= len(v.buf.lines) {
328                 y = len(v.buf.lines) - 1
329         }
330         if y < 0 {
331                 y = 0
332         }
333         if x < 0 {
334                 x = 0
335         }
336
337         x = v.cursor.GetCharPosInLine(y, x)
338         if x > Count(v.buf.lines[y]) {
339                 x = Count(v.buf.lines[y])
340         }
341         v.cursor.x = x
342         v.cursor.y = y
343         v.cursor.lastVisualX = v.cursor.GetVisualX()
344 }
345
346 // HandleEvent handles an event passed by the main loop
347 func (v *View) HandleEvent(event tcell.Event) {
348         // This bool determines whether the view is relocated at the end of the function
349         // By default it's true because most events should cause a relocate
350         relocate := true
351
352         // By default we don't update and syntax highlighting
353         v.UpdateLines(-2, 0)
354         switch e := event.(type) {
355         case *tcell.EventResize:
356                 // Window resized
357                 v.Resize(e.Size())
358         case *tcell.EventKey:
359                 switch e.Key() {
360                 case tcell.KeyUp:
361                         // Cursor up
362                         v.cursor.ResetSelection()
363                         v.cursor.Up()
364                 case tcell.KeyDown:
365                         // Cursor down
366                         v.cursor.ResetSelection()
367                         v.cursor.Down()
368                 case tcell.KeyLeft:
369                         // Cursor left
370                         v.cursor.ResetSelection()
371                         v.cursor.Left()
372                 case tcell.KeyRight:
373                         // Cursor right
374                         v.cursor.ResetSelection()
375                         v.cursor.Right()
376                 case tcell.KeyEnter:
377                         // Insert a newline
378                         if v.cursor.HasSelection() {
379                                 v.cursor.DeleteSelection()
380                                 v.cursor.ResetSelection()
381                         }
382                         v.eh.Insert(v.cursor.Loc(), "\n")
383                         v.cursor.Right()
384                         // Rehighlight the entire buffer
385                         v.UpdateLines(v.topline, v.topline+v.height)
386                         v.cursor.lastVisualX = v.cursor.GetVisualX()
387                         // v.UpdateLines(v.cursor.y-1, v.cursor.y)
388                 case tcell.KeySpace:
389                         // Insert a space
390                         if v.cursor.HasSelection() {
391                                 v.cursor.DeleteSelection()
392                                 v.cursor.ResetSelection()
393                         }
394                         v.eh.Insert(v.cursor.Loc(), " ")
395                         v.cursor.Right()
396                         v.UpdateLines(v.cursor.y, v.cursor.y)
397                 case tcell.KeyBackspace2, tcell.KeyBackspace:
398                         // Delete a character
399                         if v.cursor.HasSelection() {
400                                 v.cursor.DeleteSelection()
401                                 v.cursor.ResetSelection()
402                                 // Rehighlight the entire buffer
403                                 v.UpdateLines(v.topline, v.topline+v.height)
404                         } else if v.cursor.Loc() > 0 {
405                                 // We have to do something a bit hacky here because we want to
406                                 // delete the line by first moving left and then deleting backwards
407                                 // but the undo redo would place the cursor in the wrong place
408                                 // So instead we move left, save the position, move back, delete
409                                 // and restore the position
410                                 v.cursor.Left()
411                                 cx, cy := v.cursor.x, v.cursor.y
412                                 v.cursor.Right()
413                                 loc := v.cursor.Loc()
414                                 v.eh.Remove(loc-1, loc)
415                                 v.cursor.x, v.cursor.y = cx, cy
416                                 // Rehighlight the entire buffer
417                                 v.UpdateLines(v.topline, v.topline+v.height)
418                                 // v.UpdateLines(v.cursor.y, v.cursor.y+1)
419                         }
420                         v.cursor.lastVisualX = v.cursor.GetVisualX()
421                 case tcell.KeyTab:
422                         // Insert a tab
423                         if v.cursor.HasSelection() {
424                                 v.cursor.DeleteSelection()
425                                 v.cursor.ResetSelection()
426                         }
427                         if settings.TabsToSpaces {
428                                 v.eh.Insert(v.cursor.Loc(), Spaces(settings.TabSize))
429                                 for i := 0; i < settings.TabSize; i++ {
430                                         v.cursor.Right()
431                                 }
432                         } else {
433                                 v.eh.Insert(v.cursor.Loc(), "\t")
434                                 v.cursor.Right()
435                         }
436                         v.UpdateLines(v.cursor.y, v.cursor.y)
437                 case tcell.KeyCtrlS:
438                         v.Save()
439                 case tcell.KeyCtrlF:
440                         if v.cursor.HasSelection() {
441                                 searchStart = v.cursor.curSelection[1]
442                         } else {
443                                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
444                         }
445                         BeginSearch()
446                 case tcell.KeyCtrlN:
447                         if v.cursor.HasSelection() {
448                                 searchStart = v.cursor.curSelection[1]
449                         } else {
450                                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
451                         }
452                         messenger.Message("Find: " + lastSearch)
453                         Search(lastSearch, v, true)
454                 case tcell.KeyCtrlP:
455                         if v.cursor.HasSelection() {
456                                 searchStart = v.cursor.curSelection[0]
457                         } else {
458                                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
459                         }
460                         messenger.Message("Find: " + lastSearch)
461                         Search(lastSearch, v, false)
462                 case tcell.KeyCtrlZ:
463                         v.eh.Undo()
464                         // Rehighlight the entire buffer
465                         v.UpdateLines(v.topline, v.topline+v.height)
466                 case tcell.KeyCtrlY:
467                         v.eh.Redo()
468                         // Rehighlight the entire buffer
469                         v.UpdateLines(v.topline, v.topline+v.height)
470                 case tcell.KeyCtrlC:
471                         v.Copy()
472                         // Rehighlight the entire buffer
473                         v.UpdateLines(v.topline, v.topline+v.height)
474                 case tcell.KeyCtrlX:
475                         v.Cut()
476                         // Rehighlight the entire buffer
477                         v.UpdateLines(v.topline, v.topline+v.height)
478                 case tcell.KeyCtrlV:
479                         v.Paste()
480                         // Rehighlight the entire buffer
481                         v.UpdateLines(v.topline, v.topline+v.height)
482                 case tcell.KeyCtrlA:
483                         v.SelectAll()
484                 case tcell.KeyCtrlO:
485                         v.OpenFile()
486                         // Rehighlight the entire buffer
487                         v.UpdateLines(v.topline, v.topline+v.height)
488                 case tcell.KeyHome:
489                         v.topline = 0
490                         relocate = false
491                 case tcell.KeyEnd:
492                         v.topline = len(v.buf.lines) - 1 - v.height
493                         relocate = false
494                 case tcell.KeyPgUp:
495                         v.PageUp()
496                         relocate = false
497                 case tcell.KeyPgDn:
498                         v.PageDown()
499                         relocate = false
500                 case tcell.KeyCtrlU:
501                         v.HalfPageUp()
502                         relocate = false
503                 case tcell.KeyCtrlD:
504                         v.HalfPageDown()
505                         relocate = false
506                 case tcell.KeyRune:
507                         // Insert a character
508                         if v.cursor.HasSelection() {
509                                 v.cursor.DeleteSelection()
510                                 v.cursor.ResetSelection()
511                                 // Rehighlight the entire buffer
512                                 v.UpdateLines(v.topline, v.topline+v.height)
513                         } else {
514                                 v.UpdateLines(v.cursor.y, v.cursor.y)
515                         }
516                         v.eh.Insert(v.cursor.Loc(), string(e.Rune()))
517                         v.cursor.Right()
518                 }
519         case *tcell.EventMouse:
520                 x, y := e.Position()
521                 x -= v.lineNumOffset - v.leftCol
522                 y += v.topline
523                 // Position always seems to be off by one
524                 x--
525                 y--
526
527                 button := e.Buttons()
528
529                 switch button {
530                 case tcell.Button1:
531                         // Left click
532                         origX, origY := v.cursor.x, v.cursor.y
533                         v.MoveToMouseClick(x, y)
534
535                         if v.mouseReleased {
536                                 if (time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold) &&
537                                         (origX == v.cursor.x && origY == v.cursor.y) {
538                                         if v.doubleClick {
539                                                 // Triple click
540                                                 v.lastClickTime = time.Now()
541
542                                                 v.tripleClick = true
543                                                 v.doubleClick = false
544
545                                                 v.cursor.SelectLine()
546                                         } else {
547                                                 // Double click
548                                                 v.lastClickTime = time.Now()
549
550                                                 v.doubleClick = true
551                                                 v.tripleClick = false
552
553                                                 v.cursor.SelectWord()
554                                         }
555                                 } else {
556                                         v.doubleClick = false
557                                         v.tripleClick = false
558                                         v.lastClickTime = time.Now()
559
560                                         loc := v.cursor.Loc()
561                                         v.cursor.curSelection[0] = loc
562                                         v.cursor.curSelection[1] = loc
563                                 }
564                         } else {
565                                 if v.tripleClick {
566                                         v.cursor.AddLineToSelection()
567                                 } else if v.doubleClick {
568                                         v.cursor.AddWordToSelection()
569                                 } else {
570                                         v.cursor.curSelection[1] = v.cursor.Loc()
571                                 }
572                         }
573                         v.mouseReleased = false
574                 case tcell.ButtonNone:
575                         // Mouse event with no click
576                         if !v.mouseReleased {
577                                 // Mouse was just released
578
579                                 // Relocating here isn't really necessary because the cursor will
580                                 // be in the right place from the last mouse event
581                                 // However, if we are running in a terminal that doesn't support mouse motion
582                                 // events, this still allows the user to make selections, except only after they
583                                 // release the mouse
584
585                                 if !v.doubleClick && !v.tripleClick {
586                                         v.MoveToMouseClick(x, y)
587                                         v.cursor.curSelection[1] = v.cursor.Loc()
588                                 }
589                                 v.mouseReleased = true
590                         }
591                         // We don't want to relocate because otherwise the view will be relocated
592                         // every time the user moves the cursor
593                         relocate = false
594                 case tcell.WheelUp:
595                         // Scroll up two lines
596                         v.ScrollUp(2)
597                         // We don't want to relocate if the user is scrolling
598                         relocate = false
599                         // Rehighlight the entire buffer
600                         v.UpdateLines(v.topline, v.topline+v.height)
601                 case tcell.WheelDown:
602                         // Scroll down two lines
603                         v.ScrollDown(2)
604                         // We don't want to relocate if the user is scrolling
605                         relocate = false
606                         // Rehighlight the entire buffer
607                         v.UpdateLines(v.topline, v.topline+v.height)
608                 }
609         }
610
611         if relocate {
612                 v.Relocate()
613         }
614         if settings.Syntax {
615                 v.matches = Match(v)
616         }
617 }
618
619 // DisplayView renders the view to the screen
620 func (v *View) DisplayView() {
621         // matches := make(SyntaxMatches, len(v.buf.lines))
622         //
623         // viewStart := v.topline
624         // viewEnd := v.topline + v.height
625         // if viewEnd > len(v.buf.lines) {
626         //      viewEnd = len(v.buf.lines)
627         // }
628         //
629         // lines := v.buf.lines[viewStart:viewEnd]
630         // for i, line := range lines {
631         //      matches[i] = make([]tcell.Style, len(line))
632         // }
633
634         // The character number of the character in the top left of the screen
635
636         charNum := ToCharPos(0, v.topline, v.buf)
637
638         // Convert the length of buffer to a string, and get the length of the string
639         // We are going to have to offset by that amount
640         maxLineLength := len(strconv.Itoa(len(v.buf.lines)))
641         // + 1 for the little space after the line number
642         v.lineNumOffset = maxLineLength + 1
643
644         var highlightStyle tcell.Style
645
646         for lineN := 0; lineN < v.height; lineN++ {
647                 var x int
648                 // If the buffer is smaller than the view height
649                 // and we went too far, break
650                 if lineN+v.topline >= len(v.buf.lines) {
651                         break
652                 }
653                 line := v.buf.lines[lineN+v.topline]
654
655                 // Write the line number
656                 lineNumStyle := defStyle
657                 if style, ok := colorscheme["line-number"]; ok {
658                         lineNumStyle = style
659                 }
660                 // Write the spaces before the line number if necessary
661                 lineNum := strconv.Itoa(lineN + v.topline + 1)
662                 for i := 0; i < maxLineLength-len(lineNum); i++ {
663                         screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
664                         x++
665                 }
666                 // Write the actual line number
667                 for _, ch := range lineNum {
668                         screen.SetContent(x, lineN, ch, nil, lineNumStyle)
669                         x++
670                 }
671                 // Write the extra space
672                 screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
673                 x++
674
675                 // Write the line
676                 tabchars := 0
677                 runes := []rune(line)
678                 for colN := v.leftCol; colN < v.leftCol+v.width; colN++ {
679                         if colN >= len(runes) {
680                                 break
681                         }
682                         ch := runes[colN]
683                         var lineStyle tcell.Style
684                         // Does the current character need to be syntax highlighted?
685
686                         // if lineN >= v.updateLines[0] && lineN < v.updateLines[1] {
687                         if settings.Syntax {
688                                 highlightStyle = v.matches[lineN][colN]
689                         }
690                         // } else if lineN < len(v.lastMatches) && colN < len(v.lastMatches[lineN]) {
691                         // highlightStyle = v.lastMatches[lineN][colN]
692                         // } else {
693                         // highlightStyle = defStyle
694                         // }
695
696                         if v.cursor.HasSelection() &&
697                                 (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] ||
698                                         charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) {
699
700                                 lineStyle = defStyle.Reverse(true)
701
702                                 if style, ok := colorscheme["selection"]; ok {
703                                         lineStyle = style
704                                 }
705                         } else {
706                                 lineStyle = highlightStyle
707                         }
708                         // matches[lineN][colN] = highlightStyle
709
710                         if ch == '\t' {
711                                 screen.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
712                                 tabSize := settings.TabSize
713                                 for i := 0; i < tabSize-1; i++ {
714                                         tabchars++
715                                         if x-v.leftCol+tabchars >= v.lineNumOffset {
716                                                 screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, lineStyle)
717                                         }
718                                 }
719                         } else {
720                                 if x-v.leftCol+tabchars >= v.lineNumOffset {
721                                         screen.SetContent(x-v.leftCol+tabchars, lineN, ch, nil, lineStyle)
722                                 }
723                         }
724                         charNum++
725                         x++
726                 }
727                 // Here we are at a newline
728
729                 // The newline may be selected, in which case we should draw the selection style
730                 // with a space to represent it
731                 if v.cursor.HasSelection() &&
732                         (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] ||
733                                 charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) {
734
735                         selectStyle := defStyle.Reverse(true)
736
737                         if style, ok := colorscheme["selection"]; ok {
738                                 selectStyle = style
739                         }
740                         screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, selectStyle)
741                 }
742
743                 charNum++
744         }
745         // v.lastMatches = matches
746 }
747
748 // Display renders the view, the cursor, and statusline
749 func (v *View) Display() {
750         v.DisplayView()
751         v.cursor.Display()
752         v.sline.Display()
753 }