]> git.lizzy.rs Git - micro.git/blob - cmd/micro/view.go
Merge pull request #44 from aerth/fork1
[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                         } else if strings.ToLower(quit) == "save" || strings.ToLower(quit) == "s" {
200                                 v.Save()
201                                 return true
202                         }
203                 }
204         } else {
205                 return true
206         }
207         return false
208 }
209
210 // Save the buffer to disk
211 func (v *View) Save() {
212         // If this is an empty buffer, ask for a filename
213         if v.buf.path == "" {
214                 filename, canceled := messenger.Prompt("Filename: ")
215                 if !canceled {
216                         v.buf.path = filename
217                         v.buf.name = filename
218                 } else {
219                         return
220                 }
221         }
222         err := v.buf.Save()
223         if err != nil {
224                 messenger.Error(err.Error())
225         } else {
226                 messenger.Message("Saved " + v.buf.path)
227         }
228 }
229
230 // Copy the selection to the system clipboard
231 func (v *View) Copy() {
232         if v.cursor.HasSelection() {
233                 if !clipboard.Unsupported {
234                         clipboard.WriteAll(v.cursor.GetSelection())
235                 } else {
236                         messenger.Error("Clipboard is not supported on your system")
237                 }
238         }
239 }
240
241 // Cut the selection to the system clipboard
242 func (v *View) Cut() {
243         if v.cursor.HasSelection() {
244                 if !clipboard.Unsupported {
245                         clipboard.WriteAll(v.cursor.GetSelection())
246                         v.cursor.DeleteSelection()
247                         v.cursor.ResetSelection()
248                 } else {
249                         messenger.Error("Clipboard is not supported on your system")
250                 }
251         }
252 }
253
254 // Paste whatever is in the system clipboard into the buffer
255 // Delete and paste if the user has a selection
256 func (v *View) Paste() {
257         if !clipboard.Unsupported {
258                 if v.cursor.HasSelection() {
259                         v.cursor.DeleteSelection()
260                         v.cursor.ResetSelection()
261                 }
262                 clip, _ := clipboard.ReadAll()
263                 v.eh.Insert(v.cursor.Loc(), clip)
264                 v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
265         } else {
266                 messenger.Error("Clipboard is not supported on your system")
267         }
268 }
269
270 // SelectAll selects the entire buffer
271 func (v *View) SelectAll() {
272         v.cursor.curSelection[1] = 0
273         v.cursor.curSelection[0] = v.buf.Len()
274         // Put the cursor at the beginning
275         v.cursor.x = 0
276         v.cursor.y = 0
277 }
278
279 // OpenBuffer opens a new buffer in this view.
280 // This resets the topline, event handler and cursor.
281 func (v *View) OpenBuffer(buf *Buffer) {
282         v.buf = buf
283         v.topline = 0
284         // Put the cursor at the first spot
285         v.cursor = Cursor{
286                 x: 0,
287                 y: 0,
288                 v: v,
289         }
290         v.cursor.ResetSelection()
291
292         v.eh = NewEventHandler(v)
293
294         v.matches = Match(v)
295
296         // Set mouseReleased to true because we assume the mouse is not being pressed when
297         // the editor is opened
298         v.mouseReleased = true
299         v.lastClickTime = time.Time{}
300 }
301
302 // OpenFile opens a new file in the current view
303 // It makes sure that the current buffer can be closed first (unsaved changes)
304 func (v *View) OpenFile() {
305         if v.CanClose("Continue? (yes, no, save) ") {
306                 filename, canceled := messenger.Prompt("File to open: ")
307                 if canceled {
308                         return
309                 }
310                 file, err := ioutil.ReadFile(filename)
311
312                 if err != nil {
313                         messenger.Error(err.Error())
314                         return
315                 }
316                 v.buf = NewBuffer(string(file), filename)
317         }
318 }
319
320 // Relocate moves the view window so that the cursor is in view
321 // This is useful if the user has scrolled far away, and then starts typing
322 func (v *View) Relocate() bool {
323         ret := false
324         cy := v.cursor.y
325         if cy < v.topline {
326                 v.topline = cy
327                 ret = true
328         }
329         if cy > v.topline+v.height-1 {
330                 v.topline = cy - v.height + 1
331                 ret = true
332         }
333
334         cx := v.cursor.GetVisualX()
335         if cx < v.leftCol {
336                 v.leftCol = cx
337                 ret = true
338         }
339         if cx+v.lineNumOffset+1 > v.leftCol+v.width {
340                 v.leftCol = cx - v.width + v.lineNumOffset + 1
341                 ret = true
342         }
343         return ret
344 }
345
346 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
347 // by a mouse click
348 func (v *View) MoveToMouseClick(x, y int) {
349         if y-v.topline > v.height-1 {
350                 v.ScrollDown(1)
351                 y = v.height + v.topline - 1
352         }
353         if y >= len(v.buf.lines) {
354                 y = len(v.buf.lines) - 1
355         }
356         if y < 0 {
357                 y = 0
358         }
359         if x < 0 {
360                 x = 0
361         }
362
363         x = v.cursor.GetCharPosInLine(y, x)
364         if x > Count(v.buf.lines[y]) {
365                 x = Count(v.buf.lines[y])
366         }
367         v.cursor.x = x
368         v.cursor.y = y
369         v.cursor.lastVisualX = v.cursor.GetVisualX()
370 }
371
372 // HandleEvent handles an event passed by the main loop
373 func (v *View) HandleEvent(event tcell.Event) {
374         // This bool determines whether the view is relocated at the end of the function
375         // By default it's true because most events should cause a relocate
376         relocate := true
377
378         // By default we don't update and syntax highlighting
379         v.UpdateLines(-2, 0)
380         switch e := event.(type) {
381         case *tcell.EventResize:
382                 // Window resized
383                 v.Resize(e.Size())
384         case *tcell.EventKey:
385                 switch e.Key() {
386                 case tcell.KeyUp:
387                         // Cursor up
388                         v.cursor.ResetSelection()
389                         v.cursor.Up()
390                 case tcell.KeyDown:
391                         // Cursor down
392                         v.cursor.ResetSelection()
393                         v.cursor.Down()
394                 case tcell.KeyLeft:
395                         // Cursor left
396                         v.cursor.ResetSelection()
397                         v.cursor.Left()
398                 case tcell.KeyRight:
399                         // Cursor right
400                         v.cursor.ResetSelection()
401                         v.cursor.Right()
402                 case tcell.KeyEnter:
403                         // Insert a newline
404                         if v.cursor.HasSelection() {
405                                 v.cursor.DeleteSelection()
406                                 v.cursor.ResetSelection()
407                         }
408                         v.eh.Insert(v.cursor.Loc(), "\n")
409                         v.cursor.Right()
410                         // Rehighlight the entire buffer
411                         v.UpdateLines(v.topline, v.topline+v.height)
412                         v.cursor.lastVisualX = v.cursor.GetVisualX()
413                         // v.UpdateLines(v.cursor.y-1, v.cursor.y)
414                 case tcell.KeySpace:
415                         // Insert a space
416                         if v.cursor.HasSelection() {
417                                 v.cursor.DeleteSelection()
418                                 v.cursor.ResetSelection()
419                         }
420                         v.eh.Insert(v.cursor.Loc(), " ")
421                         v.cursor.Right()
422                         v.UpdateLines(v.cursor.y, v.cursor.y)
423                 case tcell.KeyBackspace2, tcell.KeyBackspace:
424                         // Delete a character
425                         if v.cursor.HasSelection() {
426                                 v.cursor.DeleteSelection()
427                                 v.cursor.ResetSelection()
428                                 // Rehighlight the entire buffer
429                                 v.UpdateLines(v.topline, v.topline+v.height)
430                         } else if v.cursor.Loc() > 0 {
431                                 // We have to do something a bit hacky here because we want to
432                                 // delete the line by first moving left and then deleting backwards
433                                 // but the undo redo would place the cursor in the wrong place
434                                 // So instead we move left, save the position, move back, delete
435                                 // and restore the position
436                                 v.cursor.Left()
437                                 cx, cy := v.cursor.x, v.cursor.y
438                                 v.cursor.Right()
439                                 loc := v.cursor.Loc()
440                                 v.eh.Remove(loc-1, loc)
441                                 v.cursor.x, v.cursor.y = cx, cy
442                                 // Rehighlight the entire buffer
443                                 v.UpdateLines(v.topline, v.topline+v.height)
444                                 // v.UpdateLines(v.cursor.y, v.cursor.y+1)
445                         }
446                         v.cursor.lastVisualX = v.cursor.GetVisualX()
447                 case tcell.KeyTab:
448                         // Insert a tab
449                         if v.cursor.HasSelection() {
450                                 v.cursor.DeleteSelection()
451                                 v.cursor.ResetSelection()
452                         }
453                         if settings.TabsToSpaces {
454                                 v.eh.Insert(v.cursor.Loc(), Spaces(settings.TabSize))
455                                 for i := 0; i < settings.TabSize; i++ {
456                                         v.cursor.Right()
457                                 }
458                         } else {
459                                 v.eh.Insert(v.cursor.Loc(), "\t")
460                                 v.cursor.Right()
461                         }
462                         v.UpdateLines(v.cursor.y, v.cursor.y)
463                 case tcell.KeyCtrlS:
464                         v.Save()
465                 case tcell.KeyCtrlF:
466                         if v.cursor.HasSelection() {
467                                 searchStart = v.cursor.curSelection[1]
468                         } else {
469                                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
470                         }
471                         BeginSearch()
472                 case tcell.KeyCtrlN:
473                         if v.cursor.HasSelection() {
474                                 searchStart = v.cursor.curSelection[1]
475                         } else {
476                                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
477                         }
478                         messenger.Message("Find: " + lastSearch)
479                         Search(lastSearch, v, true)
480                 case tcell.KeyCtrlP:
481                         if v.cursor.HasSelection() {
482                                 searchStart = v.cursor.curSelection[0]
483                         } else {
484                                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
485                         }
486                         messenger.Message("Find: " + lastSearch)
487                         Search(lastSearch, v, false)
488                 case tcell.KeyCtrlZ:
489                         v.eh.Undo()
490                         // Rehighlight the entire buffer
491                         v.UpdateLines(v.topline, v.topline+v.height)
492                 case tcell.KeyCtrlY:
493                         v.eh.Redo()
494                         // Rehighlight the entire buffer
495                         v.UpdateLines(v.topline, v.topline+v.height)
496                 case tcell.KeyCtrlC:
497                         v.Copy()
498                         // Rehighlight the entire buffer
499                         v.UpdateLines(v.topline, v.topline+v.height)
500                 case tcell.KeyCtrlX:
501                         v.Cut()
502                         // Rehighlight the entire buffer
503                         v.UpdateLines(v.topline, v.topline+v.height)
504                 case tcell.KeyCtrlV:
505                         v.Paste()
506                         // Rehighlight the entire buffer
507                         v.UpdateLines(v.topline, v.topline+v.height)
508                 case tcell.KeyCtrlA:
509                         v.SelectAll()
510                 case tcell.KeyCtrlO:
511                         v.OpenFile()
512                         // Rehighlight the entire buffer
513                         v.UpdateLines(v.topline, v.topline+v.height)
514                 case tcell.KeyHome:
515                         v.topline = 0
516                         relocate = false
517                 case tcell.KeyEnd:
518                         if v.height > len(v.buf.lines) {
519                                 v.topline = 0
520                         } else {
521                                 v.topline = len(v.buf.lines) - v.height
522                         }
523                         relocate = false
524                 case tcell.KeyPgUp:
525                         v.PageUp()
526                         relocate = false
527                 case tcell.KeyPgDn:
528                         v.PageDown()
529                         relocate = false
530                 case tcell.KeyCtrlU:
531                         v.HalfPageUp()
532                         relocate = false
533                 case tcell.KeyCtrlD:
534                         v.HalfPageDown()
535                         relocate = false
536                 case tcell.KeyRune:
537                         // Insert a character
538                         if v.cursor.HasSelection() {
539                                 v.cursor.DeleteSelection()
540                                 v.cursor.ResetSelection()
541                                 // Rehighlight the entire buffer
542                                 v.UpdateLines(v.topline, v.topline+v.height)
543                         } else {
544                                 v.UpdateLines(v.cursor.y, v.cursor.y)
545                         }
546                         v.eh.Insert(v.cursor.Loc(), string(e.Rune()))
547                         v.cursor.Right()
548                 }
549         case *tcell.EventMouse:
550                 x, y := e.Position()
551                 x -= v.lineNumOffset - v.leftCol
552                 y += v.topline
553                 // Position always seems to be off by one
554                 x--
555                 y--
556
557                 button := e.Buttons()
558
559                 switch button {
560                 case tcell.Button1:
561                         // Left click
562                         origX, origY := v.cursor.x, v.cursor.y
563                         v.MoveToMouseClick(x, y)
564
565                         if v.mouseReleased {
566                                 if (time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold) &&
567                                         (origX == v.cursor.x && origY == v.cursor.y) {
568                                         if v.doubleClick {
569                                                 // Triple click
570                                                 v.lastClickTime = time.Now()
571
572                                                 v.tripleClick = true
573                                                 v.doubleClick = false
574
575                                                 v.cursor.SelectLine()
576                                         } else {
577                                                 // Double click
578                                                 v.lastClickTime = time.Now()
579
580                                                 v.doubleClick = true
581                                                 v.tripleClick = false
582
583                                                 v.cursor.SelectWord()
584                                         }
585                                 } else {
586                                         v.doubleClick = false
587                                         v.tripleClick = false
588                                         v.lastClickTime = time.Now()
589
590                                         loc := v.cursor.Loc()
591                                         v.cursor.curSelection[0] = loc
592                                         v.cursor.curSelection[1] = loc
593                                 }
594                         } else {
595                                 if v.tripleClick {
596                                         v.cursor.AddLineToSelection()
597                                 } else if v.doubleClick {
598                                         v.cursor.AddWordToSelection()
599                                 } else {
600                                         v.cursor.curSelection[1] = v.cursor.Loc()
601                                 }
602                         }
603                         v.mouseReleased = false
604                 case tcell.ButtonNone:
605                         // Mouse event with no click
606                         if !v.mouseReleased {
607                                 // Mouse was just released
608
609                                 // Relocating here isn't really necessary because the cursor will
610                                 // be in the right place from the last mouse event
611                                 // However, if we are running in a terminal that doesn't support mouse motion
612                                 // events, this still allows the user to make selections, except only after they
613                                 // release the mouse
614
615                                 if !v.doubleClick && !v.tripleClick {
616                                         v.MoveToMouseClick(x, y)
617                                         v.cursor.curSelection[1] = v.cursor.Loc()
618                                 }
619                                 v.mouseReleased = true
620                         }
621                         // We don't want to relocate because otherwise the view will be relocated
622                         // every time the user moves the cursor
623                         relocate = false
624                 case tcell.WheelUp:
625                         // Scroll up two lines
626                         v.ScrollUp(2)
627                         // We don't want to relocate if the user is scrolling
628                         relocate = false
629                         // Rehighlight the entire buffer
630                         v.UpdateLines(v.topline, v.topline+v.height)
631                 case tcell.WheelDown:
632                         // Scroll down two lines
633                         v.ScrollDown(2)
634                         // We don't want to relocate if the user is scrolling
635                         relocate = false
636                         // Rehighlight the entire buffer
637                         v.UpdateLines(v.topline, v.topline+v.height)
638                 }
639         }
640
641         if relocate {
642                 v.Relocate()
643         }
644         if settings.Syntax {
645                 v.matches = Match(v)
646         }
647 }
648
649 // DisplayView renders the view to the screen
650 func (v *View) DisplayView() {
651         // matches := make(SyntaxMatches, len(v.buf.lines))
652         //
653         // viewStart := v.topline
654         // viewEnd := v.topline + v.height
655         // if viewEnd > len(v.buf.lines) {
656         //      viewEnd = len(v.buf.lines)
657         // }
658         //
659         // lines := v.buf.lines[viewStart:viewEnd]
660         // for i, line := range lines {
661         //      matches[i] = make([]tcell.Style, len(line))
662         // }
663
664         // The character number of the character in the top left of the screen
665
666         charNum := ToCharPos(0, v.topline, v.buf)
667
668         // Convert the length of buffer to a string, and get the length of the string
669         // We are going to have to offset by that amount
670         maxLineLength := len(strconv.Itoa(len(v.buf.lines)))
671         // + 1 for the little space after the line number
672         v.lineNumOffset = maxLineLength + 1
673
674         var highlightStyle tcell.Style
675
676         for lineN := 0; lineN < v.height; lineN++ {
677                 var x int
678                 // If the buffer is smaller than the view height
679                 // and we went too far, break
680                 if lineN+v.topline >= len(v.buf.lines) {
681                         break
682                 }
683                 line := v.buf.lines[lineN+v.topline]
684
685                 // Write the line number
686                 lineNumStyle := defStyle
687                 if style, ok := colorscheme["line-number"]; ok {
688                         lineNumStyle = style
689                 }
690                 // Write the spaces before the line number if necessary
691                 lineNum := strconv.Itoa(lineN + v.topline + 1)
692                 for i := 0; i < maxLineLength-len(lineNum); i++ {
693                         screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
694                         x++
695                 }
696                 // Write the actual line number
697                 for _, ch := range lineNum {
698                         screen.SetContent(x, lineN, ch, nil, lineNumStyle)
699                         x++
700                 }
701                 // Write the extra space
702                 screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
703                 x++
704
705                 // Write the line
706                 tabchars := 0
707                 runes := []rune(line)
708                 for colN := v.leftCol; colN < v.leftCol+v.width; colN++ {
709                         if colN >= len(runes) {
710                                 break
711                         }
712                         ch := runes[colN]
713                         var lineStyle tcell.Style
714                         // Does the current character need to be syntax highlighted?
715
716                         // if lineN >= v.updateLines[0] && lineN < v.updateLines[1] {
717                         if settings.Syntax {
718                                 highlightStyle = v.matches[lineN][colN]
719                         }
720                         // } else if lineN < len(v.lastMatches) && colN < len(v.lastMatches[lineN]) {
721                         // highlightStyle = v.lastMatches[lineN][colN]
722                         // } else {
723                         // highlightStyle = defStyle
724                         // }
725
726                         if v.cursor.HasSelection() &&
727                                 (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] ||
728                                         charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) {
729
730                                 lineStyle = defStyle.Reverse(true)
731
732                                 if style, ok := colorscheme["selection"]; ok {
733                                         lineStyle = style
734                                 }
735                         } else {
736                                 lineStyle = highlightStyle
737                         }
738                         // matches[lineN][colN] = highlightStyle
739
740                         if ch == '\t' {
741                                 screen.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
742                                 tabSize := settings.TabSize
743                                 for i := 0; i < tabSize-1; i++ {
744                                         tabchars++
745                                         if x-v.leftCol+tabchars >= v.lineNumOffset {
746                                                 screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, lineStyle)
747                                         }
748                                 }
749                         } else {
750                                 if x-v.leftCol+tabchars >= v.lineNumOffset {
751                                         screen.SetContent(x-v.leftCol+tabchars, lineN, ch, nil, lineStyle)
752                                 }
753                         }
754                         charNum++
755                         x++
756                 }
757                 // Here we are at a newline
758
759                 // The newline may be selected, in which case we should draw the selection style
760                 // with a space to represent it
761                 if v.cursor.HasSelection() &&
762                         (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] ||
763                                 charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) {
764
765                         selectStyle := defStyle.Reverse(true)
766
767                         if style, ok := colorscheme["selection"]; ok {
768                                 selectStyle = style
769                         }
770                         screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, selectStyle)
771                 }
772
773                 charNum++
774         }
775         // v.lastMatches = matches
776 }
777
778 // Display renders the view, the cursor, and statusline
779 func (v *View) Display() {
780         v.DisplayView()
781         v.cursor.Display()
782         v.sline.Display()
783 }