]> git.lizzy.rs Git - micro.git/blob - internal/display/bufwindow.go
Add VLoc, VLocFromLoc and LocFromVLoc
[micro.git] / internal / display / bufwindow.go
1 package display
2
3 import (
4         "strconv"
5
6         runewidth "github.com/mattn/go-runewidth"
7         "github.com/zyedidia/micro/v2/internal/buffer"
8         "github.com/zyedidia/micro/v2/internal/config"
9         "github.com/zyedidia/micro/v2/internal/screen"
10         "github.com/zyedidia/micro/v2/internal/util"
11         "github.com/zyedidia/tcell/v2"
12 )
13
14 // The BufWindow provides a way of displaying a certain section
15 // of a buffer
16 type BufWindow struct {
17         *View
18
19         // Buffer being shown in this window
20         Buf *buffer.Buffer
21
22         active bool
23
24         sline *StatusLine
25
26         bufWidth         int
27         bufHeight        int
28         gutterOffset     int
29         hasMessage       bool
30         maxLineNumLength int
31         drawDivider      bool
32 }
33
34 // NewBufWindow creates a new window at a location in the screen with a width and height
35 func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
36         w := new(BufWindow)
37         w.View = new(View)
38         w.X, w.Y, w.Width, w.Height = x, y, width, height
39         w.SetBuffer(buf)
40         w.active = true
41
42         w.sline = NewStatusLine(w)
43
44         return w
45 }
46
47 func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
48         w.Buf = b
49         b.OptionCallback = func(option string, nativeValue interface{}) {
50                 if option == "softwrap" {
51                         if nativeValue.(bool) {
52                                 w.StartCol = 0
53                         } else {
54                                 w.StartLine.Row = 0
55                         }
56                         w.Relocate()
57                 }
58         }
59 }
60
61 func (w *BufWindow) GetView() *View {
62         return w.View
63 }
64
65 func (w *BufWindow) SetView(view *View) {
66         w.View = view
67 }
68
69 func (w *BufWindow) Resize(width, height int) {
70         w.Width, w.Height = width, height
71         w.Relocate()
72 }
73
74 func (w *BufWindow) SetActive(b bool) {
75         w.active = b
76 }
77
78 func (w *BufWindow) IsActive() bool {
79         return w.active
80 }
81
82 // BufWidth returns the width of the actual buffer displayed in the window,
83 // which is usually less than the window width due to the gutter, ruler or scrollbar
84 func (w *BufWindow) BufWidth() int {
85         return w.bufWidth
86 }
87
88 // BufHeight returns the height of the actual buffer displayed in the window,
89 // which is usually less than the window height due to the statusline
90 func (w *BufWindow) BufHeight() int {
91         return w.bufHeight
92 }
93
94 func (w *BufWindow) updateDisplayInfo() {
95         b := w.Buf
96
97         w.drawDivider = false
98         if !b.Settings["statusline"].(bool) {
99                 _, h := screen.Screen.Size()
100                 infoY := h
101                 if config.GetGlobalOption("infobar").(bool) {
102                         infoY--
103                 }
104                 if w.Y+w.Height != infoY {
105                         w.drawDivider = true
106                 }
107         }
108
109         w.bufHeight = w.Height
110         if b.Settings["statusline"].(bool) || w.drawDivider {
111                 w.bufHeight--
112         }
113
114         w.hasMessage = len(b.Messages) > 0
115
116         // We need to know the string length of the largest line number
117         // so we can pad appropriately when displaying line numbers
118         w.maxLineNumLength = len(strconv.Itoa(b.LinesNum()))
119
120         w.gutterOffset = 0
121         if w.hasMessage {
122                 w.gutterOffset += 2
123         }
124         if b.Settings["diffgutter"].(bool) {
125                 w.gutterOffset++
126         }
127         if b.Settings["ruler"].(bool) {
128                 w.gutterOffset += w.maxLineNumLength + 1
129         }
130
131         w.bufWidth = w.Width - w.gutterOffset
132         if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
133                 w.bufWidth--
134         }
135 }
136
137 func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
138         tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
139         width := 0
140         bloc := buffer.Loc{0, lineN}
141         b := w.Buf.LineBytes(lineN)
142         curStyle := config.DefStyle
143         var s *tcell.Style
144         for len(b) > 0 {
145                 r, _, size := util.DecodeCharacter(b)
146
147                 curStyle, found := w.getStyle(curStyle, bloc)
148                 if found {
149                         s = &curStyle
150                 }
151
152                 w := 0
153                 switch r {
154                 case '\t':
155                         ts := tabsize - (width % tabsize)
156                         w = ts
157                 default:
158                         w = runewidth.RuneWidth(r)
159                 }
160                 if width+w > n {
161                         return b, n - width, bloc.X, s
162                 }
163                 width += w
164                 b = b[size:]
165                 bloc.X++
166         }
167         return b, n - width, bloc.X, s
168 }
169
170 // Clear resets all cells in this window to the default style
171 func (w *BufWindow) Clear() {
172         for y := 0; y < w.Height; y++ {
173                 for x := 0; x < w.Width; x++ {
174                         screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
175                 }
176         }
177 }
178
179 // Relocate moves the view window so that the cursor is in view
180 // This is useful if the user has scrolled far away, and then starts typing
181 // Returns true if the window location is moved
182 func (w *BufWindow) Relocate() bool {
183         b := w.Buf
184         height := w.bufHeight
185         ret := false
186         activeC := w.Buf.GetActiveCursor()
187         scrollmargin := int(b.Settings["scrollmargin"].(float64))
188
189         c := w.SLocFromLoc(activeC.Loc)
190         bStart := SLoc{0, 0}
191         bEnd := w.SLocFromLoc(b.End())
192
193         if c.LessThan(w.Scroll(w.StartLine, scrollmargin)) && c.GreaterThan(w.Scroll(bStart, scrollmargin-1)) {
194                 w.StartLine = w.Scroll(c, -scrollmargin)
195                 ret = true
196         } else if c.LessThan(w.StartLine) {
197                 w.StartLine = c
198                 ret = true
199         }
200         if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessThan(w.Scroll(bEnd, -scrollmargin+1)) {
201                 w.StartLine = w.Scroll(c, -height+1+scrollmargin)
202                 ret = true
203         } else if c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {
204                 w.StartLine = w.Scroll(bEnd, -height+1)
205                 ret = true
206         }
207
208         // horizontal relocation (scrolling)
209         if !b.Settings["softwrap"].(bool) {
210                 cx := activeC.GetVisualX()
211                 rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
212                 if rw == 0 {
213                         rw = 1 // tab or newline
214                 }
215
216                 if cx < w.StartCol {
217                         w.StartCol = cx
218                         ret = true
219                 }
220                 if cx+w.gutterOffset+rw > w.StartCol+w.Width {
221                         w.StartCol = cx - w.Width + w.gutterOffset + rw
222                         ret = true
223                 }
224         }
225         return ret
226 }
227
228 // LocFromVisual takes a visual location (x and y position) and returns the
229 // position in the buffer corresponding to the visual location
230 // Computing the buffer location requires essentially drawing the entire screen
231 // to account for complications like softwrap, wide characters, and horizontal scrolling
232 // If the requested position does not correspond to a buffer location it returns
233 // the nearest position
234 func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
235         b := w.Buf
236
237         maxWidth := w.gutterOffset + w.bufWidth
238
239         tabsize := int(b.Settings["tabsize"].(float64))
240         softwrap := b.Settings["softwrap"].(bool)
241
242         // this represents the current draw position
243         // within the current window
244         vloc := buffer.Loc{X: 0, Y: 0}
245         if softwrap {
246                 // the start line may be partially out of the current window
247                 vloc.Y = -w.StartLine.Row
248         }
249
250         // this represents the current draw position in the buffer (char positions)
251         bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
252
253         for ; vloc.Y < w.bufHeight; vloc.Y++ {
254                 vloc.X = w.gutterOffset
255
256                 line := b.LineBytes(bloc.Y)
257                 line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
258                 bloc.X = bslice
259
260                 draw := func() {
261                         if nColsBeforeStart <= 0 {
262                                 vloc.X++
263                         }
264                         nColsBeforeStart--
265                 }
266
267                 totalwidth := w.StartCol - nColsBeforeStart
268
269                 for len(line) > 0 {
270                         r, _, size := util.DecodeCharacter(line)
271
272                         width := 0
273
274                         switch r {
275                         case '\t':
276                                 ts := tabsize - (totalwidth % tabsize)
277                                 width = util.Min(ts, maxWidth-vloc.X)
278                                 totalwidth += ts
279                         default:
280                                 width = runewidth.RuneWidth(r)
281                                 totalwidth += width
282                         }
283
284                         // If a wide rune does not fit in the window
285                         if vloc.X+width > maxWidth && vloc.X > w.gutterOffset {
286                                 if vloc.Y+w.Y == svloc.Y {
287                                         return bloc
288                                 }
289
290                                 // We either stop or we wrap to draw the rune in the next line
291                                 if !softwrap {
292                                         break
293                                 } else {
294                                         vloc.Y++
295                                         if vloc.Y >= w.bufHeight {
296                                                 break
297                                         }
298                                         vloc.X = w.gutterOffset
299                                 }
300                         }
301
302                         draw()
303
304                         // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
305                         if width > 1 {
306                                 for i := 1; i < width; i++ {
307                                         draw()
308                                 }
309                         }
310
311                         if svloc.X < vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
312                                 return bloc
313                         }
314                         bloc.X++
315                         line = line[size:]
316
317                         // If we reach the end of the window then we either stop or we wrap for softwrap
318                         if vloc.X >= maxWidth {
319                                 if !softwrap {
320                                         break
321                                 } else {
322                                         vloc.Y++
323                                         if vloc.Y >= w.bufHeight {
324                                                 break
325                                         }
326                                         vloc.X = w.gutterOffset
327                                 }
328                         }
329                 }
330                 if vloc.Y+w.Y == svloc.Y {
331                         return bloc
332                 }
333
334                 if bloc.Y+1 >= b.LinesNum() || vloc.Y+1 >= w.bufHeight {
335                         return bloc
336                 }
337
338                 bloc.X = w.StartCol
339                 bloc.Y++
340         }
341
342         return buffer.Loc{}
343 }
344
345 func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
346         char := ' '
347         s := config.DefStyle
348         for _, m := range w.Buf.Messages {
349                 if m.Start.Y == bloc.Y || m.End.Y == bloc.Y {
350                         s = m.Style()
351                         char = '>'
352                         break
353                 }
354         }
355         screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
356         vloc.X++
357         screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
358         vloc.X++
359 }
360
361 func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
362         symbol := ' '
363         styleName := ""
364
365         switch w.Buf.DiffStatus(bloc.Y) {
366         case buffer.DSAdded:
367                 symbol = '\u258C' // Left half block
368                 styleName = "diff-added"
369         case buffer.DSModified:
370                 symbol = '\u258C' // Left half block
371                 styleName = "diff-modified"
372         case buffer.DSDeletedAbove:
373                 if !softwrapped {
374                         symbol = '\u2594' // Upper one eighth block
375                         styleName = "diff-deleted"
376                 }
377         }
378
379         style := backgroundStyle
380         if s, ok := config.Colorscheme[styleName]; ok {
381                 foreground, _, _ := s.Decompose()
382                 style = style.Foreground(foreground)
383         }
384
385         screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)
386         vloc.X++
387 }
388
389 func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
390         cursorLine := w.Buf.GetActiveCursor().Loc.Y
391         var lineInt int
392         if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
393                 lineInt = bloc.Y + 1
394         } else {
395                 lineInt = bloc.Y - cursorLine
396         }
397         lineNum := strconv.Itoa(util.Abs(lineInt))
398
399         // Write the spaces before the line number if necessary
400         for i := 0; i < w.maxLineNumLength-len(lineNum); i++ {
401                 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
402                 vloc.X++
403         }
404         // Write the actual line number
405         for _, ch := range lineNum {
406                 if softwrapped {
407                         screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
408                 } else {
409                         screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
410                 }
411                 vloc.X++
412         }
413
414         // Write the extra space
415         screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
416         vloc.X++
417 }
418
419 // getStyle returns the highlight style for the given character position
420 // If there is no change to the current highlight style it just returns that
421 func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {
422         if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
423                 s := config.GetColor(group.String())
424                 return s, true
425         }
426         return style, false
427 }
428
429 func (w *BufWindow) showCursor(x, y int, main bool) {
430         if w.active {
431                 if main {
432                         screen.ShowCursor(x, y)
433                 } else {
434                         screen.ShowFakeCursorMulti(x, y)
435                 }
436         }
437 }
438
439 // displayBuffer draws the buffer being shown in this window on the screen.Screen
440 func (w *BufWindow) displayBuffer() {
441         b := w.Buf
442
443         if w.Height <= 0 || w.Width <= 0 {
444                 return
445         }
446
447         maxWidth := w.gutterOffset + w.bufWidth
448
449         if b.ModifiedThisFrame {
450                 if b.Settings["diffgutter"].(bool) {
451                         b.UpdateDiff(func(synchronous bool) {
452                                 // If the diff was updated asynchronously, the outer call to
453                                 // displayBuffer might already be completed and we need to
454                                 // schedule a redraw in order to display the new diff.
455                                 // Note that this cannot lead to an infinite recursion
456                                 // because the modifications were cleared above so there won't
457                                 // be another call to UpdateDiff when displayBuffer is called
458                                 // during the redraw.
459                                 if !synchronous {
460                                         screen.Redraw()
461                                 }
462                         })
463                 }
464                 b.ModifiedThisFrame = false
465         }
466
467         var matchingBraces []buffer.Loc
468         // bracePairs is defined in buffer.go
469         if b.Settings["matchbrace"].(bool) {
470                 for _, bp := range buffer.BracePairs {
471                         for _, c := range b.GetCursors() {
472                                 if c.HasSelection() {
473                                         continue
474                                 }
475                                 curX := c.X
476                                 curLoc := c.Loc
477
478                                 r := c.RuneUnder(curX)
479                                 rl := c.RuneUnder(curX - 1)
480                                 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
481                                         mb, left, found := b.FindMatchingBrace(bp, curLoc)
482                                         if found {
483                                                 matchingBraces = append(matchingBraces, mb)
484                                                 if !left {
485                                                         matchingBraces = append(matchingBraces, curLoc)
486                                                 } else {
487                                                         matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
488                                                 }
489                                         }
490                                 }
491                         }
492                 }
493         }
494
495         lineNumStyle := config.DefStyle
496         if style, ok := config.Colorscheme["line-number"]; ok {
497                 lineNumStyle = style
498         }
499         curNumStyle := config.DefStyle
500         if style, ok := config.Colorscheme["current-line-number"]; ok {
501                 if !b.Settings["cursorline"].(bool) {
502                         curNumStyle = lineNumStyle
503                 } else {
504                         curNumStyle = style
505                 }
506         }
507
508         softwrap := b.Settings["softwrap"].(bool)
509         tabsize := util.IntOpt(b.Settings["tabsize"])
510         colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
511
512         // this represents the current draw position
513         // within the current window
514         vloc := buffer.Loc{X: 0, Y: 0}
515         if softwrap {
516                 // the start line may be partially out of the current window
517                 vloc.Y = -w.StartLine.Row
518         }
519
520         // this represents the current draw position in the buffer (char positions)
521         bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
522
523         cursors := b.GetCursors()
524
525         curStyle := config.DefStyle
526         for ; vloc.Y < w.bufHeight; vloc.Y++ {
527                 vloc.X = 0
528
529                 currentLine := false
530                 for _, c := range cursors {
531                         if bloc.Y == c.Y && w.active {
532                                 currentLine = true
533                                 break
534                         }
535                 }
536
537                 s := lineNumStyle
538                 if currentLine {
539                         s = curNumStyle
540                 }
541
542                 if vloc.Y >= 0 {
543                         if w.hasMessage {
544                                 w.drawGutter(&vloc, &bloc)
545                         }
546
547                         if b.Settings["diffgutter"].(bool) {
548                                 w.drawDiffGutter(s, false, &vloc, &bloc)
549                         }
550
551                         if b.Settings["ruler"].(bool) {
552                                 w.drawLineNum(s, false, &vloc, &bloc)
553                         }
554                 } else {
555                         vloc.X = w.gutterOffset
556                 }
557
558                 line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
559                 if startStyle != nil {
560                         curStyle = *startStyle
561                 }
562                 bloc.X = bslice
563
564                 draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
565                         if nColsBeforeStart <= 0 && vloc.Y >= 0 {
566                                 _, origBg, _ := style.Decompose()
567                                 _, defBg, _ := config.DefStyle.Decompose()
568
569                                 // syntax highlighting with non-default background takes precedence
570                                 // over cursor-line and color-column
571                                 dontOverrideBackground := origBg != defBg
572
573                                 for _, c := range cursors {
574                                         if c.HasSelection() &&
575                                                 (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
576                                                         bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
577                                                 // The current character is selected
578                                                 style = config.DefStyle.Reverse(true)
579
580                                                 if s, ok := config.Colorscheme["selection"]; ok {
581                                                         style = s
582                                                 }
583                                         }
584
585                                         if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
586                                                 !c.HasSelection() && c.Y == bloc.Y {
587                                                 if s, ok := config.Colorscheme["cursor-line"]; ok {
588                                                         fg, _, _ := s.Decompose()
589                                                         style = style.Background(fg)
590                                                 }
591                                         }
592                                 }
593
594                                 for _, m := range b.Messages {
595                                         if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
596                                                 bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
597                                                 style = style.Underline(true)
598                                                 break
599                                         }
600                                 }
601
602                                 if r == '\t' {
603                                         indentrunes := []rune(b.Settings["indentchar"].(string))
604                                         // if empty indentchar settings, use space
605                                         if len(indentrunes) == 0 {
606                                                 indentrunes = []rune{' '}
607                                         }
608
609                                         r = indentrunes[0]
610                                         if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
611                                                 fg, _, _ := s.Decompose()
612                                                 style = style.Foreground(fg)
613                                         }
614                                 }
615
616                                 if s, ok := config.Colorscheme["color-column"]; ok {
617                                         if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
618                                                 fg, _, _ := s.Decompose()
619                                                 style = style.Background(fg)
620                                         }
621                                 }
622
623                                 for _, mb := range matchingBraces {
624                                         if mb.X == bloc.X && mb.Y == bloc.Y {
625                                                 style = style.Underline(true)
626                                         }
627                                 }
628
629                                 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
630
631                                 if showcursor {
632                                         for _, c := range cursors {
633                                                 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
634                                                         w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
635                                                 }
636                                         }
637                                 }
638                         }
639                         if nColsBeforeStart <= 0 {
640                                 vloc.X++
641                         }
642                         nColsBeforeStart--
643                 }
644
645                 wrap := func() {
646                         vloc.X = 0
647                         if w.hasMessage {
648                                 w.drawGutter(&vloc, &bloc)
649                         }
650                         if b.Settings["diffgutter"].(bool) {
651                                 w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
652                         }
653
654                         // This will draw an empty line number because the current line is wrapped
655                         if b.Settings["ruler"].(bool) {
656                                 w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
657                         }
658                 }
659
660                 totalwidth := w.StartCol - nColsBeforeStart
661                 for len(line) > 0 {
662                         r, combc, size := util.DecodeCharacter(line)
663
664                         curStyle, _ = w.getStyle(curStyle, bloc)
665
666                         width := 0
667
668                         char := ' '
669                         switch r {
670                         case '\t':
671                                 ts := tabsize - (totalwidth % tabsize)
672                                 width = util.Min(ts, maxWidth-vloc.X)
673                                 totalwidth += ts
674                         default:
675                                 width = runewidth.RuneWidth(r)
676                                 char = '@'
677                                 totalwidth += width
678                         }
679
680                         // If a wide rune does not fit in the window
681                         if vloc.X+width > maxWidth && vloc.X > w.gutterOffset {
682                                 for vloc.X < maxWidth {
683                                         draw(' ', nil, config.DefStyle, false)
684                                 }
685
686                                 // We either stop or we wrap to draw the rune in the next line
687                                 if !softwrap {
688                                         break
689                                 } else {
690                                         vloc.Y++
691                                         if vloc.Y >= w.bufHeight {
692                                                 break
693                                         }
694                                         wrap()
695                                 }
696                         }
697
698                         draw(r, combc, curStyle, true)
699
700                         // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
701                         if width > 1 {
702                                 for i := 1; i < width; i++ {
703                                         draw(char, nil, curStyle, false)
704                                 }
705                         }
706                         bloc.X++
707                         line = line[size:]
708
709                         // If we reach the end of the window then we either stop or we wrap for softwrap
710                         if vloc.X >= maxWidth {
711                                 if !softwrap {
712                                         break
713                                 } else {
714                                         vloc.Y++
715                                         if vloc.Y >= w.bufHeight {
716                                                 break
717                                         }
718                                         wrap()
719                                 }
720                         }
721                 }
722
723                 style := config.DefStyle
724                 for _, c := range cursors {
725                         if b.Settings["cursorline"].(bool) && w.active &&
726                                 !c.HasSelection() && c.Y == bloc.Y {
727                                 if s, ok := config.Colorscheme["cursor-line"]; ok {
728                                         fg, _, _ := s.Decompose()
729                                         style = style.Background(fg)
730                                 }
731                         }
732                 }
733                 for i := vloc.X; i < maxWidth; i++ {
734                         curStyle := style
735                         if s, ok := config.Colorscheme["color-column"]; ok {
736                                 if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
737                                         fg, _, _ := s.Decompose()
738                                         curStyle = style.Background(fg)
739                                 }
740                         }
741                         screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
742                 }
743
744                 if vloc.X != maxWidth {
745                         // Display newline within a selection
746                         draw(' ', nil, config.DefStyle, true)
747                 }
748
749                 bloc.X = w.StartCol
750                 bloc.Y++
751                 if bloc.Y >= b.LinesNum() {
752                         break
753                 }
754         }
755 }
756
757 func (w *BufWindow) displayStatusLine() {
758         if w.Buf.Settings["statusline"].(bool) {
759                 w.sline.Display()
760         } else if w.drawDivider {
761                 divchars := config.GetGlobalOption("divchars").(string)
762                 if util.CharacterCountInString(divchars) != 2 {
763                         divchars = "|-"
764                 }
765
766                 _, _, size := util.DecodeCharacterInString(divchars)
767                 divchar, combc, _ := util.DecodeCharacterInString(divchars[size:])
768
769                 dividerStyle := config.DefStyle
770                 if style, ok := config.Colorscheme["divider"]; ok {
771                         dividerStyle = style
772                 }
773
774                 divreverse := config.GetGlobalOption("divreverse").(bool)
775                 if divreverse {
776                         dividerStyle = dividerStyle.Reverse(true)
777                 }
778
779                 for x := w.X; x < w.X+w.Width; x++ {
780                         screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
781                 }
782         }
783 }
784
785 func (w *BufWindow) displayScrollBar() {
786         if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
787                 scrollX := w.X + w.Width - 1
788                 barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
789                 if barsize < 1 {
790                         barsize = 1
791                 }
792                 barstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height))
793
794                 scrollBarStyle := config.DefStyle.Reverse(true)
795                 if style, ok := config.Colorscheme["scrollbar"]; ok {
796                         scrollBarStyle = style
797                 }
798
799                 for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
800                         screen.SetContent(scrollX, y, '|', nil, scrollBarStyle)
801                 }
802         }
803 }
804
805 // Display displays the buffer and the statusline
806 func (w *BufWindow) Display() {
807         w.updateDisplayInfo()
808
809         w.displayStatusLine()
810         w.displayScrollBar()
811         w.displayBuffer()
812 }