8 runewidth "github.com/mattn/go-runewidth"
9 "github.com/zyedidia/micro/internal/buffer"
10 "github.com/zyedidia/micro/internal/config"
11 "github.com/zyedidia/micro/internal/screen"
12 "github.com/zyedidia/micro/internal/util"
13 "github.com/zyedidia/tcell"
16 // The BufWindow provides a way of displaying a certain section
18 type BufWindow struct {
21 // Buffer being shown in this window
32 // NewBufWindow creates a new window at a location in the screen with a width and height
33 func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
36 w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
39 w.sline = NewStatusLine(w)
44 func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
48 func (v *View) GetView() *View {
52 func (v *View) SetView(view *View) {
56 func (w *BufWindow) Resize(width, height int) {
57 w.Width, w.Height = width, height
61 func (w *BufWindow) SetActive(b bool) {
65 func (w *BufWindow) IsActive() bool {
69 func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
70 tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
72 bloc := buffer.Loc{0, lineN}
73 b := w.Buf.LineBytes(lineN)
74 curStyle := config.DefStyle
77 r, size := utf8.DecodeRune(b)
79 curStyle, found := w.getStyle(curStyle, bloc, r)
87 ts := tabsize - (width % tabsize)
90 w = runewidth.RuneWidth(r)
93 return b, n - width, bloc.X, s
99 return b, n - width, bloc.X, s
102 // Clear resets all cells in this window to the default style
103 func (w *BufWindow) Clear() {
104 for y := 0; y < w.Height; y++ {
105 for x := 0; x < w.Width; x++ {
106 screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
111 // Bottomline returns the line number of the lowest line in the view
112 // You might think that this is obviously just v.StartLine + v.Height
113 // but if softwrap is enabled things get complicated since one buffer
114 // line can take up multiple lines in the view
115 func (w *BufWindow) Bottomline() int {
116 if !w.Buf.Settings["softwrap"].(bool) {
117 h := w.StartLine + w.Height - 1
124 l := w.LocFromVisual(buffer.Loc{0, w.Height})
126 log.Println("Bottom line:", l.Y)
130 // Relocate moves the view window so that the cursor is in view
131 // This is useful if the user has scrolled far away, and then starts typing
132 // Returns true if the window location is moved
133 func (w *BufWindow) Relocate() bool {
135 // how many buffer lines are in the view
136 height := w.Bottomline() + 1 - w.StartLine
137 log.Printf("Height: %d, w.Height: %d\n", height, w.Height)
142 if b.LinesNum() <= h {
146 activeC := w.Buf.GetActiveCursor()
148 scrollmargin := int(b.Settings["scrollmargin"].(float64))
149 if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
150 w.StartLine = cy - scrollmargin
152 } else if cy < w.StartLine {
156 if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
157 w.StartLine = cy - height + 1 + scrollmargin
159 } else if cy >= b.LinesNum()-scrollmargin && cy >= height {
160 w.StartLine = b.LinesNum() - height
164 // horizontal relocation (scrolling)
165 if !b.Settings["softwrap"].(bool) {
166 cx := activeC.GetVisualX()
171 if cx+w.gutterOffset+1 > w.StartCol+w.Width {
172 w.StartCol = cx - w.Width + w.gutterOffset + 1
179 // LocFromVisual takes a visual location (x and y position) and returns the
180 // position in the buffer corresponding to the visual location
181 // Computing the buffer location requires essentially drawing the entire screen
182 // to account for complications like softwrap, wide characters, and horizontal scrolling
183 // If the requested position does not correspond to a buffer location it returns
184 // the nearest position
185 func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
188 hasMessage := len(b.Messages) > 0
189 bufHeight := w.Height
194 // We need to know the string length of the largest line number
195 // so we can pad appropriately when displaying line numbers
196 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
198 tabsize := int(b.Settings["tabsize"].(float64))
199 softwrap := b.Settings["softwrap"].(bool)
201 // this represents the current draw position
202 // within the current window
203 vloc := buffer.Loc{X: 0, Y: 0}
205 // this represents the current draw position in the buffer (char positions)
206 bloc := buffer.Loc{X: -1, Y: w.StartLine}
208 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
213 if b.Settings["ruler"].(bool) {
214 vloc.X += maxLineNumLength + 1
217 line := b.LineBytes(bloc.Y)
218 line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
222 if nColsBeforeStart <= 0 {
228 totalwidth := w.StartCol - nColsBeforeStart
230 if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
234 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
238 r, size := utf8.DecodeRune(line)
244 ts := tabsize - (totalwidth % tabsize)
247 width = runewidth.RuneWidth(r)
250 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
252 for i := 1; i < width; i++ {
253 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
264 // If we reach the end of the window then we either stop or we wrap for softwrap
265 if vloc.X >= w.Width {
270 if vloc.Y >= bufHeight {
274 // This will draw an empty line number because the current line is wrapped
275 vloc.X += maxLineNumLength + 1
279 if vloc.Y+w.Y == svloc.Y {
283 if bloc.Y+1 >= b.LinesNum() || vloc.Y+1 >= bufHeight {
294 func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
297 for _, m := range w.Buf.Messages {
298 if m.Start.Y == bloc.Y || m.End.Y == bloc.Y {
304 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
306 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
310 func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
311 lineNum := strconv.Itoa(bloc.Y + 1)
313 // Write the spaces before the line number if necessary
314 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
315 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
318 // Write the actual line number
319 for _, ch := range lineNum {
321 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
323 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
328 // Write the extra space
329 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
333 // getStyle returns the highlight style for the given character position
334 // If there is no change to the current highlight style it just returns that
335 func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
336 if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
337 s := config.GetColor(group.String())
343 func (w *BufWindow) showCursor(x, y int, main bool) {
346 screen.Screen.ShowCursor(x, y)
348 r, _, _, _ := screen.Screen.GetContent(x, y)
349 screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
354 // displayBuffer draws the buffer being shown in this window on the screen.Screen
355 func (w *BufWindow) displayBuffer() {
358 hasMessage := len(b.Messages) > 0
359 bufHeight := w.Height
365 if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
369 if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
370 for _, c := range b.GetCursors() {
371 // rehighlight starting from where the cursor is
373 if start > 0 && b.Rehighlight(start-1) {
374 b.Highlighter.ReHighlightLine(b, start-1)
375 b.SetRehighlight(start-1, false)
378 b.Highlighter.ReHighlightStates(b, start)
379 b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
383 var matchingBraces []buffer.Loc
384 // bracePairs is defined in buffer.go
385 if b.Settings["matchbrace"].(bool) {
386 for _, bp := range buffer.BracePairs {
387 for _, c := range b.GetCursors() {
388 if c.HasSelection() {
394 r := c.RuneUnder(curX)
395 rl := c.RuneUnder(curX - 1)
396 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
397 mb, left := b.FindMatchingBrace(bp, curLoc)
398 matchingBraces = append(matchingBraces, mb)
400 matchingBraces = append(matchingBraces, curLoc)
402 matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
409 lineNumStyle := config.DefStyle
410 if style, ok := config.Colorscheme["line-number"]; ok {
413 curNumStyle := config.DefStyle
414 if style, ok := config.Colorscheme["current-line-number"]; ok {
418 // We need to know the string length of the largest line number
419 // so we can pad appropriately when displaying line numbers
420 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
422 softwrap := b.Settings["softwrap"].(bool)
423 tabsize := util.IntOpt(b.Settings["tabsize"])
424 colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
426 // this represents the current draw position
427 // within the current window
428 vloc := buffer.Loc{X: 0, Y: 0}
430 // this represents the current draw position in the buffer (char positions)
431 bloc := buffer.Loc{X: -1, Y: w.StartLine}
433 cursors := b.GetCursors()
435 curStyle := config.DefStyle
436 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
440 w.drawGutter(&vloc, &bloc)
443 if b.Settings["ruler"].(bool) {
445 for _, c := range cursors {
446 if bloc.Y == c.Y && w.active {
451 w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
454 w.gutterOffset = vloc.X
456 line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
457 if startStyle != nil {
458 curStyle = *startStyle
462 draw := func(r rune, style tcell.Style, showcursor bool) {
463 if nColsBeforeStart <= 0 {
464 for _, c := range cursors {
465 if c.HasSelection() &&
466 (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
467 bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
468 // The current character is selected
469 style = config.DefStyle.Reverse(true)
471 if s, ok := config.Colorscheme["selection"]; ok {
476 if b.Settings["cursorline"].(bool) && w.active &&
477 !c.HasSelection() && c.Y == bloc.Y {
478 if s, ok := config.Colorscheme["cursor-line"]; ok {
479 fg, _, _ := s.Decompose()
480 style = style.Background(fg)
485 for _, m := range b.Messages {
486 if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
487 bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
488 style = style.Underline(true)
494 indentrunes := []rune(b.Settings["indentchar"].(string))
495 // if empty indentchar settings, use space
496 if indentrunes == nil || len(indentrunes) == 0 {
497 indentrunes = []rune{' '}
501 if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
502 fg, _, _ := s.Decompose()
503 style = style.Foreground(fg)
507 if s, ok := config.Colorscheme["color-column"]; ok {
508 if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
509 fg, _, _ := s.Decompose()
510 style = style.Background(fg)
514 for _, mb := range matchingBraces {
515 if mb.X == bloc.X && mb.Y == bloc.Y {
516 style = style.Underline(true)
520 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
523 for _, c := range cursors {
524 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
525 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
534 totalwidth := w.StartCol - nColsBeforeStart
536 r, size := utf8.DecodeRune(line)
537 curStyle, _ = w.getStyle(curStyle, bloc, r)
539 draw(r, curStyle, true)
546 ts := tabsize - (totalwidth % tabsize)
549 width = runewidth.RuneWidth(r)
553 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
555 for i := 1; i < width; i++ {
556 draw(char, curStyle, false)
564 // If we reach the end of the window then we either stop or we wrap for softwrap
565 if vloc.X >= bufWidth {
570 if vloc.Y >= bufHeight {
574 // This will draw an empty line number because the current line is wrapped
575 w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
580 style := config.DefStyle
581 for _, c := range cursors {
582 if b.Settings["cursorline"].(bool) && w.active &&
583 !c.HasSelection() && c.Y == bloc.Y {
584 if s, ok := config.Colorscheme["cursor-line"]; ok {
585 fg, _, _ := s.Decompose()
586 style = style.Background(fg)
590 for i := vloc.X; i < bufWidth; i++ {
592 if s, ok := config.Colorscheme["color-column"]; ok {
593 if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
594 fg, _, _ := s.Decompose()
595 curStyle = style.Background(fg)
598 screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
601 for _, c := range cursors {
602 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
603 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
607 draw(' ', curStyle, false)
611 if bloc.Y >= b.LinesNum() {
617 func (w *BufWindow) displayStatusLine() {
618 _, h := screen.Screen.Size()
620 if config.GetGlobalOption("infobar").(bool) {
624 if w.Buf.Settings["statusline"].(bool) {
627 } else if w.Y+w.Height != infoY {
629 for x := w.X; x < w.X+w.Width; x++ {
630 screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
637 func (w *BufWindow) displayScrollBar() {
638 if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
639 scrollX := w.X + w.Width - 1
640 bufHeight := w.Height
644 barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
648 barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
649 for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
650 screen.Screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
655 // Display displays the buffer and the statusline
656 func (w *BufWindow) Display() {
657 w.displayStatusLine()