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
33 // NewBufWindow creates a new window at a location in the screen with a width and height
34 func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
37 w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
38 w.lineHeight = make([]int, height)
41 w.sline = NewStatusLine(w)
46 func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
50 func (v *View) GetView() *View {
54 func (v *View) SetView(view *View) {
58 func (w *BufWindow) Resize(width, height int) {
59 w.Width, w.Height = width, height
60 w.lineHeight = make([]int, height)
61 w.hasCalcHeight = false
62 // This recalculates lineHeight
63 w.GetMouseLoc(buffer.Loc{width, height})
66 func (w *BufWindow) SetActive(b bool) {
70 func (w *BufWindow) IsActive() bool {
74 func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
75 tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
77 bloc := buffer.Loc{0, lineN}
78 b := w.Buf.LineBytes(lineN)
79 curStyle := config.DefStyle
82 r, size := utf8.DecodeRune(b)
84 curStyle, found := w.getStyle(curStyle, bloc, r)
92 ts := tabsize - (width % tabsize)
95 w = runewidth.RuneWidth(r)
98 return b, n - width, bloc.X, s
104 return b, n - width, bloc.X, s
107 // Clear resets all cells in this window to the default style
108 func (w *BufWindow) Clear() {
109 for y := 0; y < w.Height; y++ {
110 for x := 0; x < w.Width; x++ {
111 screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
116 // Bottomline returns the line number of the lowest line in the view
117 // You might think that this is obviously just v.StartLine + v.Height
118 // but if softwrap is enabled things get complicated since one buffer
119 // line can take up multiple lines in the view
120 func (w *BufWindow) Bottomline() int {
123 // TODO: possible non-softwrap optimization
124 // if !b.Settings["softwrap"].(bool) {
125 // return w.StartLine + w.Height
129 for _, l := range w.lineHeight {
139 // Relocate moves the view window so that the cursor is in view
140 // This is useful if the user has scrolled far away, and then starts typing
141 // Returns true if the window location is moved
142 func (w *BufWindow) Relocate() bool {
144 // how many buffer lines are in the view
145 height := w.Bottomline() + 1 - w.StartLine
150 if b.LinesNum() <= h || !w.hasCalcHeight {
154 activeC := w.Buf.GetActiveCursor()
156 scrollmargin := int(b.Settings["scrollmargin"].(float64))
157 if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
158 w.StartLine = cy - scrollmargin
160 } else if cy < w.StartLine {
164 if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
165 w.StartLine = cy - height + 1 + scrollmargin
167 } else if cy >= b.LinesNum()-scrollmargin && cy >= height {
168 w.StartLine = b.LinesNum() - height
172 // horizontal relocation (scrolling)
173 if !b.Settings["softwrap"].(bool) {
174 cx := activeC.GetVisualX()
179 if cx+w.gutterOffset+1 > w.StartCol+w.Width {
180 w.StartCol = cx - w.Width + w.gutterOffset + 1
187 func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc {
190 // TODO: possible non-softwrap optimization
191 // if !b.Settings["softwrap"].(bool) {
192 // l := b.LineBytes(svloc.Y)
193 // return buffer.Loc{b.GetActiveCursor().GetCharPosInLine(l, svloc.X), svloc.Y}
196 hasMessage := len(b.Messages) > 0
197 bufHeight := w.Height
202 // We need to know the string length of the largest line number
203 // so we can pad appropriately when displaying line numbers
204 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
206 tabsize := int(b.Settings["tabsize"].(float64))
207 softwrap := b.Settings["softwrap"].(bool)
209 // this represents the current draw position
210 // within the current window
211 vloc := buffer.Loc{X: 0, Y: 0}
213 // this represents the current draw position in the buffer (char positions)
214 bloc := buffer.Loc{X: -1, Y: w.StartLine}
216 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
221 if b.Settings["ruler"].(bool) {
222 vloc.X += maxLineNumLength + 1
225 line := b.LineBytes(bloc.Y)
226 line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
230 if nColsBeforeStart <= 0 {
236 totalwidth := w.StartCol - nColsBeforeStart
238 if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
242 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
246 r, size := utf8.DecodeRune(line)
252 ts := tabsize - (totalwidth % tabsize)
255 width = runewidth.RuneWidth(r)
258 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
260 for i := 1; i < width; i++ {
261 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
272 // If we reach the end of the window then we either stop or we wrap for softwrap
273 if vloc.X >= w.Width {
278 if vloc.Y >= bufHeight {
282 // This will draw an empty line number because the current line is wrapped
283 vloc.X += maxLineNumLength + 1
287 if vloc.Y+w.Y == svloc.Y {
293 if bloc.Y >= b.LinesNum() {
301 func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
304 for _, m := range w.Buf.Messages {
305 if m.Start.Y == bloc.Y || m.End.Y == bloc.Y {
311 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
313 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
317 func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
318 lineNum := strconv.Itoa(bloc.Y + 1)
320 // Write the spaces before the line number if necessary
321 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
322 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
325 // Write the actual line number
326 for _, ch := range lineNum {
328 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
330 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
335 // Write the extra space
336 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
340 // getStyle returns the highlight style for the given character position
341 // If there is no change to the current highlight style it just returns that
342 func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
343 if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
344 s := config.GetColor(group.String())
350 func (w *BufWindow) showCursor(x, y int, main bool) {
353 screen.Screen.ShowCursor(x, y)
355 r, _, _, _ := screen.Screen.GetContent(x, y)
356 screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
361 // displayBuffer draws the buffer being shown in this window on the screen.Screen
362 func (w *BufWindow) displayBuffer() {
365 hasMessage := len(b.Messages) > 0
366 bufHeight := w.Height
371 w.hasCalcHeight = true
373 if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
374 if start > 0 && b.Rehighlight(start-1) {
375 b.Highlighter.ReHighlightLine(b, start-1)
376 b.SetRehighlight(start-1, false)
379 b.Highlighter.ReHighlightStates(b, start)
381 b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
384 lineNumStyle := config.DefStyle
385 if style, ok := config.Colorscheme["line-number"]; ok {
388 curNumStyle := config.DefStyle
389 if style, ok := config.Colorscheme["current-line-number"]; ok {
393 // We need to know the string length of the largest line number
394 // so we can pad appropriately when displaying line numbers
395 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
397 softwrap := b.Settings["softwrap"].(bool)
398 tabsize := util.IntOpt(b.Settings["tabsize"])
399 colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
401 // this represents the current draw position
402 // within the current window
403 vloc := buffer.Loc{X: 0, Y: 0}
405 // this represents the current draw position in the buffer (char positions)
406 bloc := buffer.Loc{X: -1, Y: w.StartLine}
408 cursors := b.GetCursors()
410 curStyle := config.DefStyle
411 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
415 w.drawGutter(&vloc, &bloc)
418 if b.Settings["ruler"].(bool) {
420 for _, c := range cursors {
421 if bloc.Y == c.Y && w.active {
426 w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
429 w.gutterOffset = vloc.X
431 line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
432 if startStyle != nil {
433 curStyle = *startStyle
437 draw := func(r rune, style tcell.Style, showcursor bool) {
438 if nColsBeforeStart <= 0 {
439 for _, c := range cursors {
440 if c.HasSelection() &&
441 (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
442 bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
443 // The current character is selected
444 style = config.DefStyle.Reverse(true)
446 if s, ok := config.Colorscheme["selection"]; ok {
451 if b.Settings["cursorline"].(bool) && w.active &&
452 !c.HasSelection() && c.Y == bloc.Y {
453 if s, ok := config.Colorscheme["cursor-line"]; ok {
454 fg, _, _ := s.Decompose()
455 style = style.Background(fg)
461 if s, ok := config.Colorscheme["indent-char"]; ok {
464 indentrunes := []rune(b.Settings["indentchar"].(string))
465 // if empty indentchar settings, use space
466 if indentrunes == nil || len(indentrunes) == 0 {
467 indentrunes = []rune{' '}
473 if s, ok := config.Colorscheme["color-column"]; ok {
474 if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
475 fg, _, _ := s.Decompose()
476 style = style.Background(fg)
480 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
483 for _, c := range cursors {
484 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
485 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
494 w.lineHeight[vloc.Y] = bloc.Y
496 totalwidth := w.StartCol - nColsBeforeStart
498 r, size := utf8.DecodeRune(line)
499 curStyle, _ = w.getStyle(curStyle, bloc, r)
501 draw(r, curStyle, true)
508 ts := tabsize - (totalwidth % tabsize)
511 width = runewidth.RuneWidth(r)
515 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
517 for i := 1; i < width; i++ {
518 draw(char, curStyle, false)
526 // If we reach the end of the window then we either stop or we wrap for softwrap
527 if vloc.X >= w.Width {
532 if vloc.Y >= bufHeight {
536 w.lineHeight[vloc.Y] = bloc.Y
537 // This will draw an empty line number because the current line is wrapped
538 w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
543 style := config.DefStyle
544 for _, c := range cursors {
545 if b.Settings["cursorline"].(bool) && w.active &&
546 !c.HasSelection() && c.Y == bloc.Y {
547 if s, ok := config.Colorscheme["cursor-line"]; ok {
548 fg, _, _ := s.Decompose()
549 style = style.Background(fg)
553 for i := vloc.X; i < w.Width; i++ {
555 if s, ok := config.Colorscheme["color-column"]; ok {
556 if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
557 fg, _, _ := s.Decompose()
558 curStyle = style.Background(fg)
561 screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
564 for _, c := range cursors {
565 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
566 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
572 if bloc.Y >= b.LinesNum() {
578 func (w *BufWindow) displayStatusLine() {
579 _, h := screen.Screen.Size()
581 if config.GetGlobalOption("infobar").(bool) {
585 if w.Buf.Settings["statusline"].(bool) {
588 } else if w.Y+w.Height != infoY {
590 for x := w.X; x < w.X+w.Width; x++ {
591 screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
598 // Display displays the buffer and the statusline
599 func (w *BufWindow) Display() {
600 w.displayStatusLine()