7 runewidth "github.com/mattn/go-runewidth"
8 "github.com/zyedidia/micro/internal/buffer"
9 "github.com/zyedidia/micro/internal/config"
10 "github.com/zyedidia/micro/internal/screen"
11 "github.com/zyedidia/micro/internal/util"
12 "github.com/zyedidia/tcell"
15 // The BufWindow provides a way of displaying a certain section
17 type BufWindow struct {
20 // Buffer being shown in this window
31 // NewBufWindow creates a new window at a location in the screen with a width and height
32 func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
35 w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
38 w.sline = NewStatusLine(w)
43 func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
47 func (w *BufWindow) GetView() *View {
51 func (w *BufWindow) SetView(view *View) {
55 func (w *BufWindow) Resize(width, height int) {
56 w.Width, w.Height = width, height
60 func (w *BufWindow) SetActive(b bool) {
64 func (w *BufWindow) IsActive() bool {
68 func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
69 tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
71 bloc := buffer.Loc{0, lineN}
72 b := w.Buf.LineBytes(lineN)
73 curStyle := config.DefStyle
76 r, size := utf8.DecodeRune(b)
78 curStyle, found := w.getStyle(curStyle, bloc, r)
86 ts := tabsize - (width % tabsize)
89 w = runewidth.RuneWidth(r)
92 return b, n - width, bloc.X, s
98 return b, n - width, bloc.X, s
101 // Clear resets all cells in this window to the default style
102 func (w *BufWindow) Clear() {
103 for y := 0; y < w.Height; y++ {
104 for x := 0; x < w.Width; x++ {
105 screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
110 // Bottomline returns the line number of the lowest line in the view
111 // You might think that this is obviously just v.StartLine + v.Height
112 // but if softwrap is enabled things get complicated since one buffer
113 // line can take up multiple lines in the view
114 func (w *BufWindow) Bottomline() int {
115 if !w.Buf.Settings["softwrap"].(bool) {
116 h := w.StartLine + w.Height - 1
123 l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
128 // Relocate moves the view window so that the cursor is in view
129 // This is useful if the user has scrolled far away, and then starts typing
130 // Returns true if the window location is moved
131 func (w *BufWindow) Relocate() bool {
133 // how many buffer lines are in the view
134 height := w.Bottomline() + 1 - w.StartLine
139 if b.LinesNum() <= h {
143 activeC := w.Buf.GetActiveCursor()
145 scrollmargin := int(b.Settings["scrollmargin"].(float64))
146 if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
147 w.StartLine = cy - scrollmargin
149 } else if cy < w.StartLine {
153 if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
154 w.StartLine = cy - height + 1 + scrollmargin
156 } else if cy >= b.LinesNum()-scrollmargin && cy >= height {
157 w.StartLine = b.LinesNum() - height
161 // horizontal relocation (scrolling)
162 if !b.Settings["softwrap"].(bool) {
163 cx := activeC.GetVisualX()
168 if cx+w.gutterOffset+1 > w.StartCol+w.Width {
169 w.StartCol = cx - w.Width + w.gutterOffset + 1
176 // LocFromVisual takes a visual location (x and y position) and returns the
177 // position in the buffer corresponding to the visual location
178 // Computing the buffer location requires essentially drawing the entire screen
179 // to account for complications like softwrap, wide characters, and horizontal scrolling
180 // If the requested position does not correspond to a buffer location it returns
181 // the nearest position
182 func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
185 hasMessage := len(b.Messages) > 0
186 bufHeight := w.Height
192 if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
196 // We need to know the string length of the largest line number
197 // so we can pad appropriately when displaying line numbers
198 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
200 tabsize := int(b.Settings["tabsize"].(float64))
201 softwrap := b.Settings["softwrap"].(bool)
203 // this represents the current draw position
204 // within the current window
205 vloc := buffer.Loc{X: 0, Y: 0}
207 // this represents the current draw position in the buffer (char positions)
208 bloc := buffer.Loc{X: -1, Y: w.StartLine}
210 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
215 if b.Settings["diffgutter"].(bool) {
218 if b.Settings["ruler"].(bool) {
219 vloc.X += maxLineNumLength + 1
222 line := b.LineBytes(bloc.Y)
223 line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
227 if nColsBeforeStart <= 0 {
233 totalwidth := w.StartCol - nColsBeforeStart
235 if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
239 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
243 r, size := utf8.DecodeRune(line)
249 ts := tabsize - (totalwidth % tabsize)
252 width = runewidth.RuneWidth(r)
255 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
257 for i := 1; i < width; i++ {
258 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
269 // If we reach the end of the window then we either stop or we wrap for softwrap
270 if vloc.X >= bufWidth {
275 if vloc.Y >= bufHeight {
278 vloc.X = w.gutterOffset
282 if vloc.Y+w.Y == svloc.Y {
286 if bloc.Y+1 >= b.LinesNum() || vloc.Y+1 >= bufHeight {
297 func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
300 for _, m := range w.Buf.Messages {
301 if m.Start.Y == bloc.Y || m.End.Y == bloc.Y {
307 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
309 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
313 func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
317 switch w.Buf.DiffStatus(bloc.Y) {
319 symbol = '\u258C' // Left half block
320 styleName = "diff-added"
321 case buffer.DSModified:
322 symbol = '\u258C' // Left half block
323 styleName = "diff-modified"
324 case buffer.DSDeletedAbove:
326 symbol = '\u2594' // Upper one eighth block
327 styleName = "diff-deleted"
331 style := backgroundStyle
332 if s, ok := config.Colorscheme[styleName]; ok {
333 foreground, _, _ := s.Decompose()
334 style = style.Foreground(foreground)
337 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)
341 func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
342 lineNum := strconv.Itoa(bloc.Y + 1)
344 // Write the spaces before the line number if necessary
345 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
346 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
349 // Write the actual line number
350 for _, ch := range lineNum {
352 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
354 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
359 // Write the extra space
360 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
364 // getStyle returns the highlight style for the given character position
365 // If there is no change to the current highlight style it just returns that
366 func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
367 if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
368 s := config.GetColor(group.String())
374 func (w *BufWindow) showCursor(x, y int, main bool) {
377 screen.ShowCursor(x, y)
379 screen.ShowFakeCursorMulti(x, y)
384 // displayBuffer draws the buffer being shown in this window on the screen.Screen
385 func (w *BufWindow) displayBuffer() {
388 if w.Height <= 0 || w.Width <= 0 {
392 hasMessage := len(b.Messages) > 0
393 bufHeight := w.Height
399 if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
403 if len(b.Modifications) > 0 {
404 if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
405 for _, r := range b.Modifications {
406 rx := util.Clamp(r.X, 0, b.LinesNum())
407 ry := util.Clamp(r.Y, 0, b.LinesNum())
409 for i := rx; i <= ry; i++ {
410 final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
412 b.Highlighter.HighlightMatches(b, rx, final+1)
416 b.ClearModifications()
418 if b.Settings["diffgutter"].(bool) {
419 b.UpdateDiff(func(synchronous bool) {
420 // If the diff was updated asynchronously, the outer call to
421 // displayBuffer might already be completed and we need to
422 // schedule a redraw in order to display the new diff.
423 // Note that this cannot lead to an infinite recursion
424 // because the modifications were cleared above so there won't
425 // be another call to UpdateDiff when displayBuffer is called
426 // during the redraw.
434 var matchingBraces []buffer.Loc
435 // bracePairs is defined in buffer.go
436 if b.Settings["matchbrace"].(bool) {
437 for _, bp := range buffer.BracePairs {
438 for _, c := range b.GetCursors() {
439 if c.HasSelection() {
445 r := c.RuneUnder(curX)
446 rl := c.RuneUnder(curX - 1)
447 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
448 mb, left, found := b.FindMatchingBrace(bp, curLoc)
450 matchingBraces = append(matchingBraces, mb)
452 matchingBraces = append(matchingBraces, curLoc)
454 matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
462 lineNumStyle := config.DefStyle
463 if style, ok := config.Colorscheme["line-number"]; ok {
466 curNumStyle := config.DefStyle
467 if style, ok := config.Colorscheme["current-line-number"]; ok {
468 if !b.Settings["cursorline"].(bool) {
469 curNumStyle = lineNumStyle
475 // We need to know the string length of the largest line number
476 // so we can pad appropriately when displaying line numbers
477 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
479 softwrap := b.Settings["softwrap"].(bool)
480 tabsize := util.IntOpt(b.Settings["tabsize"])
481 colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
483 // this represents the current draw position
484 // within the current window
485 vloc := buffer.Loc{X: 0, Y: 0}
487 // this represents the current draw position in the buffer (char positions)
488 bloc := buffer.Loc{X: -1, Y: w.StartLine}
490 cursors := b.GetCursors()
492 curStyle := config.DefStyle
493 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
497 for _, c := range cursors {
498 if bloc.Y == c.Y && w.active {
510 w.drawGutter(&vloc, &bloc)
513 if b.Settings["diffgutter"].(bool) {
514 w.drawDiffGutter(s, false, &vloc, &bloc)
517 if b.Settings["ruler"].(bool) {
518 w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
521 w.gutterOffset = vloc.X
523 line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
524 if startStyle != nil {
525 curStyle = *startStyle
529 draw := func(r rune, style tcell.Style, showcursor bool) {
530 if nColsBeforeStart <= 0 {
531 for _, c := range cursors {
532 if c.HasSelection() &&
533 (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
534 bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
535 // The current character is selected
536 style = config.DefStyle.Reverse(true)
538 if s, ok := config.Colorscheme["selection"]; ok {
543 if b.Settings["cursorline"].(bool) && w.active &&
544 !c.HasSelection() && c.Y == bloc.Y {
545 if s, ok := config.Colorscheme["cursor-line"]; ok {
546 fg, _, _ := s.Decompose()
547 style = style.Background(fg)
552 for _, m := range b.Messages {
553 if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
554 bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
555 style = style.Underline(true)
561 indentrunes := []rune(b.Settings["indentchar"].(string))
562 // if empty indentchar settings, use space
563 if indentrunes == nil || len(indentrunes) == 0 {
564 indentrunes = []rune{' '}
568 if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
569 fg, _, _ := s.Decompose()
570 style = style.Foreground(fg)
574 if s, ok := config.Colorscheme["color-column"]; ok {
575 if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
576 fg, _, _ := s.Decompose()
577 style = style.Background(fg)
581 for _, mb := range matchingBraces {
582 if mb.X == bloc.X && mb.Y == bloc.Y {
583 style = style.Underline(true)
587 screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
590 for _, c := range cursors {
591 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
592 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
601 totalwidth := w.StartCol - nColsBeforeStart
603 r, size := utf8.DecodeRune(line)
604 curStyle, _ = w.getStyle(curStyle, bloc, r)
606 draw(r, curStyle, true)
613 ts := tabsize - (totalwidth % tabsize)
616 width = runewidth.RuneWidth(r)
620 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
622 for i := 1; i < width; i++ {
623 draw(char, curStyle, false)
631 // If we reach the end of the window then we either stop or we wrap for softwrap
632 if vloc.X >= bufWidth {
637 if vloc.Y >= bufHeight {
642 w.drawGutter(&vloc, &bloc)
644 if b.Settings["diffgutter"].(bool) {
645 w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
648 // This will draw an empty line number because the current line is wrapped
649 if b.Settings["ruler"].(bool) {
650 w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
656 style := config.DefStyle
657 for _, c := range cursors {
658 if b.Settings["cursorline"].(bool) && w.active &&
659 !c.HasSelection() && c.Y == bloc.Y {
660 if s, ok := config.Colorscheme["cursor-line"]; ok {
661 fg, _, _ := s.Decompose()
662 style = style.Background(fg)
666 for i := vloc.X; i < bufWidth; i++ {
668 if s, ok := config.Colorscheme["color-column"]; ok {
669 if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
670 fg, _, _ := s.Decompose()
671 curStyle = style.Background(fg)
674 screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
677 if vloc.X != bufWidth {
678 draw(' ', curStyle, true)
683 if bloc.Y >= b.LinesNum() {
689 func (w *BufWindow) displayStatusLine() {
690 _, h := screen.Screen.Size()
692 if config.GetGlobalOption("infobar").(bool) {
696 if w.Buf.Settings["statusline"].(bool) {
699 } else if w.Y+w.Height != infoY {
701 for x := w.X; x < w.X+w.Width; x++ {
702 screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
709 func (w *BufWindow) displayScrollBar() {
710 if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
711 scrollX := w.X + w.Width - 1
712 bufHeight := w.Height
716 barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
720 barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
721 for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
722 screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
727 // Display displays the buffer and the statusline
728 func (w *BufWindow) Display() {
729 w.displayStatusLine()