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
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 {
38 w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
39 w.lineHeight = make([]int, height)
42 w.sline = NewStatusLine(w)
47 func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
51 func (v *View) GetView() *View {
55 func (v *View) SetView(view *View) {
59 func (w *BufWindow) Resize(width, height int) {
60 w.Width, w.Height = width, height
61 w.lineHeight = make([]int, height)
62 w.hasCalcHeight = false
63 // This recalculates lineHeight
64 w.GetMouseLoc(buffer.Loc{width, height})
67 func (w *BufWindow) SetActive(b bool) {
71 func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
72 tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
74 bloc := buffer.Loc{0, lineN}
75 b := w.Buf.LineBytes(lineN)
76 curStyle := config.DefStyle
79 r, size := utf8.DecodeRune(b)
81 curStyle, found := w.getStyle(curStyle, bloc, r)
89 ts := tabsize - (width % tabsize)
92 w = runewidth.RuneWidth(r)
95 return b, n - width, bloc.X, s
101 return b, n - width, bloc.X, s
104 // Clear resets all cells in this window to the default style
105 func (w *BufWindow) Clear() {
106 for y := 0; y < w.Height; y++ {
107 for x := 0; x < w.Width; x++ {
108 screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
113 // Bottomline returns the line number of the lowest line in the view
114 // You might think that this is obviously just v.StartLine + v.Height
115 // but if softwrap is enabled things get complicated since one buffer
116 // line can take up multiple lines in the view
117 func (w *BufWindow) Bottomline() int {
120 // TODO: possible non-softwrap optimization
121 // if !b.Settings["softwrap"].(bool) {
122 // return w.StartLine + w.Height
126 for _, l := range w.lineHeight {
136 // Relocate moves the view window so that the cursor is in view
137 // This is useful if the user has scrolled far away, and then starts typing
138 // Returns true if the window location is moved
139 func (w *BufWindow) Relocate() bool {
141 // how many buffer lines are in the view
142 height := w.Bottomline() + 1 - w.StartLine
147 if b.LinesNum() <= h || !w.hasCalcHeight {
151 activeC := w.Buf.GetActiveCursor()
153 log.Println("RELOCATE", w.StartLine, cy, height)
154 scrollmargin := int(b.Settings["scrollmargin"].(float64))
155 if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
157 w.StartLine = cy - scrollmargin
159 } else if cy < w.StartLine {
164 if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
166 w.StartLine = cy - height + 1 + scrollmargin
168 } else if cy >= b.LinesNum()-scrollmargin && cy >= height {
170 w.StartLine = b.LinesNum() - height
173 log.Println("RELOCATE DONE", w.StartLine)
175 // horizontal relocation (scrolling)
176 if !b.Settings["softwrap"].(bool) {
177 cx := activeC.GetVisualX()
182 if cx+w.gutterOffset+1 > w.StartCol+w.Width {
183 w.StartCol = cx - w.Width + w.gutterOffset + 1
190 func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc {
193 // TODO: possible non-softwrap optimization
194 // if !b.Settings["softwrap"].(bool) {
195 // l := b.LineBytes(svloc.Y)
196 // return buffer.Loc{b.GetActiveCursor().GetCharPosInLine(l, svloc.X), svloc.Y}
199 hasMessage := len(b.Messages) > 0
200 bufHeight := w.Height
205 // We need to know the string length of the largest line number
206 // so we can pad appropriately when displaying line numbers
207 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
209 tabsize := int(b.Settings["tabsize"].(float64))
210 softwrap := b.Settings["softwrap"].(bool)
212 // this represents the current draw position
213 // within the current window
214 vloc := buffer.Loc{X: 0, Y: 0}
216 // this represents the current draw position in the buffer (char positions)
217 bloc := buffer.Loc{X: -1, Y: w.StartLine}
219 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
224 if b.Settings["ruler"].(bool) {
225 vloc.X += maxLineNumLength + 1
228 line := b.LineBytes(bloc.Y)
229 line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
233 if nColsBeforeStart <= 0 {
239 totalwidth := w.StartCol - nColsBeforeStart
241 if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
245 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
249 r, size := utf8.DecodeRune(line)
255 ts := tabsize - (totalwidth % tabsize)
258 width = runewidth.RuneWidth(r)
261 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
263 for i := 1; i < width; i++ {
264 if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
275 // If we reach the end of the window then we either stop or we wrap for softwrap
276 if vloc.X >= w.Width {
281 if vloc.Y >= bufHeight {
285 // This will draw an empty line number because the current line is wrapped
286 vloc.X += maxLineNumLength + 1
290 if vloc.Y+w.Y == svloc.Y {
296 if bloc.Y >= b.LinesNum() {
304 func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
307 for _, m := range w.Buf.Messages {
308 if m.Start.Y == bloc.Y || m.End.Y == bloc.Y {
314 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
316 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
320 func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
321 lineNum := strconv.Itoa(bloc.Y + 1)
323 // Write the spaces before the line number if necessary
324 for i := 0; i < maxLineNumLength-len(lineNum); i++ {
325 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
328 // Write the actual line number
329 for _, ch := range lineNum {
331 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
333 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
338 // Write the extra space
339 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
343 // getStyle returns the highlight style for the given character position
344 // If there is no change to the current highlight style it just returns that
345 func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
346 if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
347 s := config.GetColor(group.String())
353 func (w *BufWindow) showCursor(x, y int, main bool) {
356 screen.Screen.ShowCursor(x, y)
358 r, _, _, _ := screen.Screen.GetContent(x, y)
359 screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
364 // displayBuffer draws the buffer being shown in this window on the screen.Screen
365 func (w *BufWindow) displayBuffer() {
366 log.Println("STARTLINE", w.StartLine)
369 hasMessage := len(b.Messages) > 0
370 bufHeight := w.Height
375 w.hasCalcHeight = true
377 if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
378 if start > 0 && b.Rehighlight(start-1) {
379 b.Highlighter.ReHighlightLine(b, start-1)
380 b.SetRehighlight(start-1, false)
383 b.Highlighter.ReHighlightStates(b, start)
385 b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
388 lineNumStyle := config.DefStyle
389 if style, ok := config.Colorscheme["line-number"]; ok {
392 curNumStyle := config.DefStyle
393 if style, ok := config.Colorscheme["current-line-number"]; ok {
397 // We need to know the string length of the largest line number
398 // so we can pad appropriately when displaying line numbers
399 maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
401 softwrap := b.Settings["softwrap"].(bool)
402 tabsize := util.IntOpt(b.Settings["tabsize"])
403 colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
405 // this represents the current draw position
406 // within the current window
407 vloc := buffer.Loc{X: 0, Y: 0}
409 // this represents the current draw position in the buffer (char positions)
410 bloc := buffer.Loc{X: -1, Y: w.StartLine}
412 cursors := b.GetCursors()
414 curStyle := config.DefStyle
415 for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
419 w.drawGutter(&vloc, &bloc)
422 if b.Settings["ruler"].(bool) {
424 for _, c := range cursors {
425 if bloc.Y == c.Y && w.active {
430 w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
433 w.gutterOffset = vloc.X
435 line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
436 if startStyle != nil {
437 curStyle = *startStyle
441 draw := func(r rune, style tcell.Style, showcursor bool) {
442 if nColsBeforeStart <= 0 {
443 for _, c := range cursors {
444 if c.HasSelection() &&
445 (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
446 bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
447 // The current character is selected
448 style = config.DefStyle.Reverse(true)
450 if s, ok := config.Colorscheme["selection"]; ok {
455 if b.Settings["cursorline"].(bool) && w.active &&
456 !c.HasSelection() && c.Y == bloc.Y {
457 if s, ok := config.Colorscheme["cursor-line"]; ok {
458 fg, _, _ := s.Decompose()
459 style = style.Background(fg)
465 if s, ok := config.Colorscheme["indent-char"]; ok {
468 indentrunes := []rune(b.Settings["indentchar"].(string))
469 // if empty indentchar settings, use space
470 if indentrunes == nil || len(indentrunes) == 0 {
471 indentrunes = []rune{' '}
477 if s, ok := config.Colorscheme["color-column"]; ok {
478 if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
479 fg, _, _ := s.Decompose()
480 style = style.Background(fg)
484 screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
487 for _, c := range cursors {
488 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
489 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
498 w.lineHeight[vloc.Y] = bloc.Y
500 totalwidth := w.StartCol - nColsBeforeStart
502 r, size := utf8.DecodeRune(line)
503 curStyle, _ = w.getStyle(curStyle, bloc, r)
505 draw(r, curStyle, true)
512 ts := tabsize - (totalwidth % tabsize)
515 width = runewidth.RuneWidth(r)
519 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
521 for i := 1; i < width; i++ {
522 draw(char, curStyle, false)
530 // If we reach the end of the window then we either stop or we wrap for softwrap
531 if vloc.X >= w.Width {
536 if vloc.Y >= bufHeight {
540 w.lineHeight[vloc.Y] = bloc.Y
541 // This will draw an empty line number because the current line is wrapped
542 w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
547 style := config.DefStyle
548 for _, c := range cursors {
549 if b.Settings["cursorline"].(bool) && w.active &&
550 !c.HasSelection() && c.Y == bloc.Y {
551 if s, ok := config.Colorscheme["cursor-line"]; ok {
552 fg, _, _ := s.Decompose()
553 style = style.Background(fg)
557 for i := vloc.X; i < w.Width; i++ {
559 if s, ok := config.Colorscheme["color-column"]; ok {
560 if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
561 fg, _, _ := s.Decompose()
562 curStyle = style.Background(fg)
565 screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
568 for _, c := range cursors {
569 if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
570 w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
576 if bloc.Y >= b.LinesNum() {
582 func (w *BufWindow) displayStatusLine() {
583 _, h := screen.Screen.Size()
585 if config.GetGlobalOption("infobar").(bool) {
589 if w.Buf.Settings["statusline"].(bool) {
592 } else if w.Y+w.Height != infoY {
594 for x := w.X; x < w.X+w.Width; x++ {
595 screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
602 // Display displays the buffer and the statusline
603 func (w *BufWindow) Display() {
604 w.displayStatusLine()