diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 566bcb96..e8ace8ba 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -166,6 +166,20 @@ func (h *BufHandler) HandleEvent(event tcell.Event) { h.DoMouseEvent(me, e) } h.Buf.MergeCursors() + + // Display any gutter messages for this line + c := h.Buf.GetActiveCursor() + none := true + for _, m := range h.Buf.Messages { + if c.Y == m.Start.Y || c.Y == m.End.Y { + InfoBar.GutterMessage(m.Msg) + none = false + break + } + } + if none && InfoBar.HasGutter { + InfoBar.ClearGutter() + } } // DoKeyEvent executes a key event by finding the action it is bound diff --git a/cmd/micro/buffer/buffer.go b/cmd/micro/buffer/buffer.go index ffacc16b..96b76dd1 100644 --- a/cmd/micro/buffer/buffer.go +++ b/cmd/micro/buffer/buffer.go @@ -104,6 +104,8 @@ type Buffer struct { // Type of the buffer (e.g. help, raw, scratch etc..) Type BufType + + Messages []*Message } // NewBufferFromFile opens a new buffer using the given path diff --git a/cmd/micro/buffer/message.go b/cmd/micro/buffer/message.go new file mode 100644 index 00000000..b1049971 --- /dev/null +++ b/cmd/micro/buffer/message.go @@ -0,0 +1,80 @@ +package buffer + +import ( + "log" + + "github.com/zyedidia/micro/cmd/micro/config" + "github.com/zyedidia/tcell" +) + +type MsgType int + +const ( + MTInfo = iota + MTWarning + MTError +) + +type Message struct { + Msg string + Start, End Loc + Kind MsgType + Owner int +} + +func NewMessage(owner int, msg string, start, end Loc, kind MsgType) *Message { + return &Message{ + Msg: msg, + Start: start, + End: end, + Kind: kind, + Owner: owner, + } +} + +func NewMessageAtLine(owner int, msg string, line int, kind MsgType) *Message { + start := Loc{-1, line} + end := start + return NewMessage(owner, msg, start, end, kind) +} + +func (m *Message) Style() tcell.Style { + switch m.Kind { + case MTInfo: + if style, ok := config.Colorscheme["gutter-info"]; ok { + return style + } + case MTWarning: + if style, ok := config.Colorscheme["gutter-warning"]; ok { + return style + } + case MTError: + if style, ok := config.Colorscheme["gutter-error"]; ok { + log.Println("Return error") + return style + } + } + return config.DefStyle +} + +func (b *Buffer) AddMessage(m *Message) { + b.Messages = append(b.Messages, m) +} + +func (b *Buffer) removeMsg(i int) { + copy(b.Messages[i:], b.Messages[i+1:]) + b.Messages[len(b.Messages)-1] = nil + b.Messages = b.Messages[:len(b.Messages)-1] +} + +func (b *Buffer) ClearMessages(owner int) { + for i := len(b.Messages) - 1; i >= 0; i-- { + if b.Messages[i].Owner == owner { + b.removeMsg(i) + } + } +} + +func (b *Buffer) ClearAllMessages() { + b.Messages = make([]*Message, 0) +} diff --git a/cmd/micro/display/bufwindow.go b/cmd/micro/display/bufwindow.go new file mode 100644 index 00000000..10b50237 --- /dev/null +++ b/cmd/micro/display/bufwindow.go @@ -0,0 +1,540 @@ +package display + +import ( + "strconv" + "unicode/utf8" + + runewidth "github.com/mattn/go-runewidth" + "github.com/zyedidia/micro/cmd/micro/buffer" + "github.com/zyedidia/micro/cmd/micro/config" + "github.com/zyedidia/micro/cmd/micro/screen" + "github.com/zyedidia/micro/cmd/micro/util" + "github.com/zyedidia/tcell" +) + +// The BufWindow provides a way of displaying a certain section +// of a buffer +type BufWindow struct { + *View + + // Buffer being shown in this window + Buf *buffer.Buffer + + active bool + + sline *StatusLine + + lineHeight []int + gutterOffset int +} + +// NewBufWindow creates a new window at a location in the screen with a width and height +func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow { + w := new(BufWindow) + w.View = new(View) + w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf + w.lineHeight = make([]int, height) + w.active = true + + w.sline = NewStatusLine(w) + + return w +} + +func (v *View) GetView() *View { + return v +} + +func (v *View) SetView(view *View) { + v = view +} + +func (w *BufWindow) Resize(width, height int) { + w.Width, w.Height = width, height + w.lineHeight = make([]int, height) +} + +func (w *BufWindow) SetActive(b bool) { + w.active = b +} + +func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) { + tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) + width := 0 + bloc := buffer.Loc{0, lineN} + b := w.Buf.LineBytes(lineN) + curStyle := config.DefStyle + var s *tcell.Style + for len(b) > 0 { + r, size := utf8.DecodeRune(b) + + curStyle, found := w.getStyle(curStyle, bloc, r) + if found { + s = &curStyle + } + + w := 0 + switch r { + case '\t': + ts := tabsize - (width % tabsize) + w = ts + default: + w = runewidth.RuneWidth(r) + } + if width+w > n { + return b, n - width, bloc.X, s + } + width += w + b = b[size:] + bloc.X++ + } + return b, n - width, bloc.X, s +} + +// Clear resets all cells in this window to the default style +func (w *BufWindow) Clear() { + for y := 0; y < w.Height; y++ { + for x := 0; x < w.Width; x++ { + screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle) + } + } +} + +// Bottomline returns the line number of the lowest line in the view +// You might think that this is obviously just v.StartLine + v.Height +// but if softwrap is enabled things get complicated since one buffer +// line can take up multiple lines in the view +func (w *BufWindow) Bottomline() int { + // b := w.Buf + + // TODO: possible non-softwrap optimization + // if !b.Settings["softwrap"].(bool) { + // return w.StartLine + w.Height + // } + + prev := 0 + for _, l := range w.lineHeight { + if l >= prev { + prev = l + } else { + break + } + } + return prev +} + +// Relocate moves the view window so that the cursor is in view +// This is useful if the user has scrolled far away, and then starts typing +// Returns true if the window location is moved +func (w *BufWindow) Relocate() bool { + b := w.Buf + height := w.Bottomline() + 1 - w.StartLine + ret := false + activeC := w.Buf.GetActiveCursor() + cy := activeC.Y + scrollmargin := int(b.Settings["scrollmargin"].(float64)) + if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 { + w.StartLine = cy - scrollmargin + ret = true + } else if cy < w.StartLine { + w.StartLine = cy + ret = true + } + if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin { + w.StartLine = cy - height + 1 + scrollmargin + ret = true + } else if cy >= b.LinesNum()-scrollmargin && cy >= height { + w.StartLine = b.LinesNum() - height + ret = true + } + + // horizontal relocation (scrolling) + if !b.Settings["softwrap"].(bool) { + cx := activeC.GetVisualX() + if cx < w.StartCol { + w.StartCol = cx + ret = true + } + if cx+w.gutterOffset+1 > w.StartCol+w.Width { + w.StartCol = cx - w.Width + w.gutterOffset + 1 + ret = true + } + } + return ret +} + +func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc { + b := w.Buf + + // TODO: possible non-softwrap optimization + // if !b.Settings["softwrap"].(bool) { + // l := b.LineBytes(svloc.Y) + // return buffer.Loc{b.GetActiveCursor().GetCharPosInLine(l, svloc.X), svloc.Y} + // } + + hasMessage := len(b.Messages) > 0 + bufHeight := w.Height + if b.Settings["statusline"].(bool) { + bufHeight-- + } + + // We need to know the string length of the largest line number + // so we can pad appropriately when displaying line numbers + maxLineNumLength := len(strconv.Itoa(b.LinesNum())) + + tabsize := int(b.Settings["tabsize"].(float64)) + softwrap := b.Settings["softwrap"].(bool) + + // this represents the current draw position + // within the current window + vloc := buffer.Loc{X: 0, Y: 0} + + // this represents the current draw position in the buffer (char positions) + bloc := buffer.Loc{X: -1, Y: w.StartLine} + + for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { + vloc.X = 0 + if hasMessage { + vloc.X += 2 + } + if b.Settings["ruler"].(bool) { + vloc.X += maxLineNumLength + 1 + } + + line := b.LineBytes(bloc.Y) + line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize) + bloc.X = bslice + + draw := func() { + if nColsBeforeStart <= 0 { + vloc.X++ + } + nColsBeforeStart-- + } + + w.lineHeight[vloc.Y] = bloc.Y + + totalwidth := w.StartCol - nColsBeforeStart + + if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y { + return bloc + } + for len(line) > 0 { + if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y { + return bloc + } + + r, size := utf8.DecodeRune(line) + draw() + width := 0 + + switch r { + case '\t': + ts := tabsize - (totalwidth % tabsize) + width = ts + default: + width = runewidth.RuneWidth(r) + } + + // Draw any extra characters either spaces for tabs or @ for incomplete wide runes + if width > 1 { + for i := 1; i < width; i++ { + if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y { + return bloc + } + draw() + } + } + bloc.X++ + line = line[size:] + + totalwidth += width + + // If we reach the end of the window then we either stop or we wrap for softwrap + if vloc.X >= w.Width { + if !softwrap { + break + } else { + vloc.Y++ + if vloc.Y >= bufHeight { + break + } + vloc.X = 0 + w.lineHeight[vloc.Y] = bloc.Y + // This will draw an empty line number because the current line is wrapped + vloc.X += maxLineNumLength + 1 + } + } + } + if vloc.Y+w.Y == svloc.Y { + return bloc + } + + bloc.X = w.StartCol + bloc.Y++ + if bloc.Y >= b.LinesNum() { + break + } + } + + return buffer.Loc{} +} + +func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) { + char := ' ' + s := config.DefStyle + for _, m := range w.Buf.Messages { + if m.Start.Y == bloc.Y || m.End.Y == bloc.Y { + s = m.Style() + char = '>' + break + } + } + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) + vloc.X++ + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) + vloc.X++ +} + +func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) { + lineNum := strconv.Itoa(bloc.Y + 1) + + // Write the spaces before the line number if necessary + for i := 0; i < maxLineNumLength-len(lineNum); i++ { + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) + vloc.X++ + } + // Write the actual line number + for _, ch := range lineNum { + if softwrapped { + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) + } else { + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle) + } + vloc.X++ + } + + // Write the extra space + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) + vloc.X++ +} + +// getStyle returns the highlight style for the given character position +// If there is no change to the current highlight style it just returns that +func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) { + if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok { + s := config.GetColor(group.String()) + return s, true + } + return style, false +} + +func (w *BufWindow) showCursor(x, y int, main bool) { + if w.active { + if main { + screen.Screen.ShowCursor(x, y) + } else { + r, _, _, _ := screen.Screen.GetContent(x, y) + screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true)) + } + } +} + +// displayBuffer draws the buffer being shown in this window on the screen.Screen +func (w *BufWindow) displayBuffer() { + b := w.Buf + + hasMessage := len(b.Messages) > 0 + bufHeight := w.Height + if b.Settings["statusline"].(bool) { + bufHeight-- + } + + // TODO: Rehighlighting + start := w.StartLine + if b.Settings["syntax"].(bool) && b.SyntaxDef != nil { + if start > 0 && b.Rehighlight(start-1) { + b.Highlighter.ReHighlightLine(b, start-1) + b.SetRehighlight(start-1, false) + } + + b.Highlighter.ReHighlightStates(b, start) + + b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight) + } + + lineNumStyle := config.DefStyle + if style, ok := config.Colorscheme["line-number"]; ok { + lineNumStyle = style + } + curNumStyle := config.DefStyle + if style, ok := config.Colorscheme["current-line-number"]; ok { + curNumStyle = style + } + + // We need to know the string length of the largest line number + // so we can pad appropriately when displaying line numbers + maxLineNumLength := len(strconv.Itoa(b.LinesNum())) + + tabsize := int(b.Settings["tabsize"].(float64)) + softwrap := b.Settings["softwrap"].(bool) + + // this represents the current draw position + // within the current window + vloc := buffer.Loc{X: 0, Y: 0} + + // this represents the current draw position in the buffer (char positions) + bloc := buffer.Loc{X: -1, Y: w.StartLine} + + cursors := b.GetCursors() + + curStyle := config.DefStyle + for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { + vloc.X = 0 + + if hasMessage { + w.drawGutter(&vloc, &bloc) + } + + if b.Settings["ruler"].(bool) { + s := lineNumStyle + for _, c := range cursors { + if bloc.Y == c.Y && w.active { + s = curNumStyle + break + } + } + w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc) + } + + w.gutterOffset = vloc.X + + line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y) + if startStyle != nil { + curStyle = *startStyle + } + bloc.X = bslice + + draw := func(r rune, style tcell.Style, showcursor bool) { + if nColsBeforeStart <= 0 { + for _, c := range cursors { + if c.HasSelection() && + (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || + bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) { + // The current character is selected + style = config.DefStyle.Reverse(true) + + if s, ok := config.Colorscheme["selection"]; ok { + style = s + } + } + + if b.Settings["cursorline"].(bool) && w.active && + !c.HasSelection() && c.Y == bloc.Y { + if s, ok := config.Colorscheme["cursor-line"]; ok { + fg, _, _ := s.Decompose() + style = style.Background(fg) + } + } + } + + screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style) + + if showcursor { + for _, c := range cursors { + if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() { + w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0) + } + } + } + vloc.X++ + } + nColsBeforeStart-- + } + + w.lineHeight[vloc.Y] = bloc.Y + + totalwidth := w.StartCol - nColsBeforeStart + for len(line) > 0 { + r, size := utf8.DecodeRune(line) + curStyle, _ = w.getStyle(curStyle, bloc, r) + + draw(r, curStyle, true) + + width := 0 + + char := ' ' + switch r { + case '\t': + ts := tabsize - (totalwidth % tabsize) + width = ts + default: + width = runewidth.RuneWidth(r) + char = '@' + } + + // Draw any extra characters either spaces for tabs or @ for incomplete wide runes + if width > 1 { + for i := 1; i < width; i++ { + draw(char, curStyle, false) + } + } + bloc.X++ + line = line[size:] + + totalwidth += width + + // If we reach the end of the window then we either stop or we wrap for softwrap + if vloc.X >= w.Width { + if !softwrap { + break + } else { + vloc.Y++ + if vloc.Y >= bufHeight { + break + } + vloc.X = 0 + w.lineHeight[vloc.Y] = bloc.Y + // This will draw an empty line number because the current line is wrapped + w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc) + } + } + } + + for _, c := range cursors { + if b.Settings["cursorline"].(bool) && w.active && + !c.HasSelection() && c.Y == bloc.Y { + style := config.DefStyle + if s, ok := config.Colorscheme["cursor-line"]; ok { + fg, _, _ := s.Decompose() + style = style.Background(fg) + } + for i := vloc.X; i < w.Width; i++ { + screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, style) + } + } + } + + for _, c := range cursors { + if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() { + w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0) + } + } + + bloc.X = w.StartCol + bloc.Y++ + if bloc.Y >= b.LinesNum() { + break + } + } +} + +func (w *BufWindow) displayStatusLine() { + w.sline.Display() +} + +// Display displays the buffer and the statusline +func (w *BufWindow) Display() { + w.displayBuffer() + w.displayStatusLine() +} diff --git a/cmd/micro/display/window.go b/cmd/micro/display/window.go index 5bba2153..c83a453a 100644 --- a/cmd/micro/display/window.go +++ b/cmd/micro/display/window.go @@ -1,15 +1,7 @@ package display import ( - "strconv" - "unicode/utf8" - - runewidth "github.com/mattn/go-runewidth" "github.com/zyedidia/micro/cmd/micro/buffer" - "github.com/zyedidia/micro/cmd/micro/config" - "github.com/zyedidia/micro/cmd/micro/screen" - "github.com/zyedidia/micro/cmd/micro/util" - "github.com/zyedidia/tcell" ) type View struct { @@ -32,504 +24,3 @@ type Window interface { Resize(w, h int) SetActive(b bool) } - -// The BufWindow provides a way of displaying a certain section -// of a buffer -type BufWindow struct { - *View - - // Buffer being shown in this window - Buf *buffer.Buffer - - active bool - - sline *StatusLine - - lineHeight []int - gutterOffset int -} - -// NewBufWindow creates a new window at a location in the screen with a width and height -func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow { - w := new(BufWindow) - w.View = new(View) - w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf - w.lineHeight = make([]int, height) - w.active = true - - w.sline = NewStatusLine(w) - - return w -} - -func (v *View) GetView() *View { - return v -} - -func (v *View) SetView(view *View) { - v = view -} - -func (w *BufWindow) Resize(width, height int) { - w.Width, w.Height = width, height - w.lineHeight = make([]int, height) -} - -func (w *BufWindow) SetActive(b bool) { - w.active = b -} - -func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) { - tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) - width := 0 - bloc := buffer.Loc{0, lineN} - b := w.Buf.LineBytes(lineN) - curStyle := config.DefStyle - var s *tcell.Style - for len(b) > 0 { - r, size := utf8.DecodeRune(b) - - curStyle, found := w.getStyle(curStyle, bloc, r) - if found { - s = &curStyle - } - - w := 0 - switch r { - case '\t': - ts := tabsize - (width % tabsize) - w = ts - default: - w = runewidth.RuneWidth(r) - } - if width+w > n { - return b, n - width, bloc.X, s - } - width += w - b = b[size:] - bloc.X++ - } - return b, n - width, bloc.X, s -} - -// Clear resets all cells in this window to the default style -func (w *BufWindow) Clear() { - for y := 0; y < w.Height; y++ { - for x := 0; x < w.Width; x++ { - screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle) - } - } -} - -// Bottomline returns the line number of the lowest line in the view -// You might think that this is obviously just v.StartLine + v.Height -// but if softwrap is enabled things get complicated since one buffer -// line can take up multiple lines in the view -func (w *BufWindow) Bottomline() int { - // b := w.Buf - - // TODO: possible non-softwrap optimization - // if !b.Settings["softwrap"].(bool) { - // return w.StartLine + w.Height - // } - - prev := 0 - for _, l := range w.lineHeight { - if l >= prev { - prev = l - } else { - break - } - } - return prev -} - -// Relocate moves the view window so that the cursor is in view -// This is useful if the user has scrolled far away, and then starts typing -// Returns true if the window location is moved -func (w *BufWindow) Relocate() bool { - b := w.Buf - height := w.Bottomline() + 1 - w.StartLine - ret := false - activeC := w.Buf.GetActiveCursor() - cy := activeC.Y - scrollmargin := int(b.Settings["scrollmargin"].(float64)) - if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 { - w.StartLine = cy - scrollmargin - ret = true - } else if cy < w.StartLine { - w.StartLine = cy - ret = true - } - if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin { - w.StartLine = cy - height + 1 + scrollmargin - ret = true - } else if cy >= b.LinesNum()-scrollmargin && cy >= height { - w.StartLine = b.LinesNum() - height - ret = true - } - - // horizontal relocation (scrolling) - if !b.Settings["softwrap"].(bool) { - cx := activeC.GetVisualX() - if cx < w.StartCol { - w.StartCol = cx - ret = true - } - if cx+w.gutterOffset+1 > w.StartCol+w.Width { - w.StartCol = cx - w.Width + w.gutterOffset + 1 - ret = true - } - } - return ret -} - -func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc { - b := w.Buf - - // TODO: possible non-softwrap optimization - // if !b.Settings["softwrap"].(bool) { - // l := b.LineBytes(svloc.Y) - // return buffer.Loc{b.GetActiveCursor().GetCharPosInLine(l, svloc.X), svloc.Y} - // } - - bufHeight := w.Height - if b.Settings["statusline"].(bool) { - bufHeight-- - } - - // We need to know the string length of the largest line number - // so we can pad appropriately when displaying line numbers - maxLineNumLength := len(strconv.Itoa(b.LinesNum())) - - tabsize := int(b.Settings["tabsize"].(float64)) - softwrap := b.Settings["softwrap"].(bool) - - // this represents the current draw position - // within the current window - vloc := buffer.Loc{X: 0, Y: 0} - - // this represents the current draw position in the buffer (char positions) - bloc := buffer.Loc{X: -1, Y: w.StartLine} - - for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { - vloc.X = 0 - if b.Settings["ruler"].(bool) { - vloc.X += maxLineNumLength + 1 - } - - line := b.LineBytes(bloc.Y) - line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize) - bloc.X = bslice - - draw := func() { - if nColsBeforeStart <= 0 { - vloc.X++ - } - nColsBeforeStart-- - } - - w.lineHeight[vloc.Y] = bloc.Y - - totalwidth := w.StartCol - nColsBeforeStart - - if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y { - return bloc - } - for len(line) > 0 { - if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y { - return bloc - } - - r, size := utf8.DecodeRune(line) - draw() - width := 0 - - switch r { - case '\t': - ts := tabsize - (totalwidth % tabsize) - width = ts - default: - width = runewidth.RuneWidth(r) - } - - // Draw any extra characters either spaces for tabs or @ for incomplete wide runes - if width > 1 { - for i := 1; i < width; i++ { - if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y { - return bloc - } - draw() - } - } - bloc.X++ - line = line[size:] - - totalwidth += width - - // If we reach the end of the window then we either stop or we wrap for softwrap - if vloc.X >= w.Width { - if !softwrap { - break - } else { - vloc.Y++ - if vloc.Y >= bufHeight { - break - } - vloc.X = 0 - w.lineHeight[vloc.Y] = bloc.Y - // This will draw an empty line number because the current line is wrapped - vloc.X += maxLineNumLength + 1 - } - } - } - if vloc.Y+w.Y == svloc.Y { - return bloc - } - - bloc.X = w.StartCol - bloc.Y++ - if bloc.Y >= b.LinesNum() { - break - } - } - - return buffer.Loc{} -} - -func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) { - lineNum := strconv.Itoa(bloc.Y + 1) - - // Write the spaces before the line number if necessary - for i := 0; i < maxLineNumLength-len(lineNum); i++ { - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) - vloc.X++ - } - // Write the actual line number - for _, ch := range lineNum { - if softwrapped { - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) - } else { - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle) - } - vloc.X++ - } - - // Write the extra space - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) - vloc.X++ -} - -// getStyle returns the highlight style for the given character position -// If there is no change to the current highlight style it just returns that -func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) { - if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok { - s := config.GetColor(group.String()) - return s, true - } - return style, false -} - -func (w *BufWindow) showCursor(x, y int, main bool) { - if w.active { - if main { - screen.Screen.ShowCursor(x, y) - } else { - r, _, _, _ := screen.Screen.GetContent(x, y) - screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true)) - } - } -} - -// displayBuffer draws the buffer being shown in this window on the screen.Screen -func (w *BufWindow) displayBuffer() { - b := w.Buf - - bufHeight := w.Height - if b.Settings["statusline"].(bool) { - bufHeight-- - } - - // TODO: Rehighlighting - start := w.StartLine - if b.Settings["syntax"].(bool) && b.SyntaxDef != nil { - if start > 0 && b.Rehighlight(start-1) { - b.Highlighter.ReHighlightLine(b, start-1) - b.SetRehighlight(start-1, false) - } - - b.Highlighter.ReHighlightStates(b, start) - - b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight) - } - - lineNumStyle := config.DefStyle - if style, ok := config.Colorscheme["line-number"]; ok { - lineNumStyle = style - } - curNumStyle := config.DefStyle - if style, ok := config.Colorscheme["current-line-number"]; ok { - curNumStyle = style - } - - // We need to know the string length of the largest line number - // so we can pad appropriately when displaying line numbers - maxLineNumLength := len(strconv.Itoa(b.LinesNum())) - - tabsize := int(b.Settings["tabsize"].(float64)) - softwrap := b.Settings["softwrap"].(bool) - - // this represents the current draw position - // within the current window - vloc := buffer.Loc{X: 0, Y: 0} - - // this represents the current draw position in the buffer (char positions) - bloc := buffer.Loc{X: -1, Y: w.StartLine} - - cursors := b.GetCursors() - - curStyle := config.DefStyle - for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ { - vloc.X = 0 - if b.Settings["ruler"].(bool) { - s := lineNumStyle - for _, c := range cursors { - if bloc.Y == c.Y && w.active { - s = curNumStyle - break - } - } - w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc) - } - - w.gutterOffset = vloc.X - - line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y) - if startStyle != nil { - curStyle = *startStyle - } - bloc.X = bslice - - draw := func(r rune, style tcell.Style, showcursor bool) { - if nColsBeforeStart <= 0 { - for _, c := range cursors { - if c.HasSelection() && - (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || - bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) { - // The current character is selected - style = config.DefStyle.Reverse(true) - - if s, ok := config.Colorscheme["selection"]; ok { - style = s - } - } - - if b.Settings["cursorline"].(bool) && w.active && - !c.HasSelection() && c.Y == bloc.Y { - if s, ok := config.Colorscheme["cursor-line"]; ok { - fg, _, _ := s.Decompose() - style = style.Background(fg) - } - } - } - - screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style) - - if showcursor { - for _, c := range cursors { - if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() { - w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0) - } - } - } - vloc.X++ - } - nColsBeforeStart-- - } - - w.lineHeight[vloc.Y] = bloc.Y - - totalwidth := w.StartCol - nColsBeforeStart - for len(line) > 0 { - r, size := utf8.DecodeRune(line) - curStyle, _ = w.getStyle(curStyle, bloc, r) - - draw(r, curStyle, true) - - width := 0 - - char := ' ' - switch r { - case '\t': - ts := tabsize - (totalwidth % tabsize) - width = ts - default: - width = runewidth.RuneWidth(r) - char = '@' - } - - // Draw any extra characters either spaces for tabs or @ for incomplete wide runes - if width > 1 { - for i := 1; i < width; i++ { - draw(char, curStyle, false) - } - } - bloc.X++ - line = line[size:] - - totalwidth += width - - // If we reach the end of the window then we either stop or we wrap for softwrap - if vloc.X >= w.Width { - if !softwrap { - break - } else { - vloc.Y++ - if vloc.Y >= bufHeight { - break - } - vloc.X = 0 - w.lineHeight[vloc.Y] = bloc.Y - // This will draw an empty line number because the current line is wrapped - w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc) - } - } - } - - for _, c := range cursors { - if b.Settings["cursorline"].(bool) && w.active && - !c.HasSelection() && c.Y == bloc.Y { - style := config.DefStyle - if s, ok := config.Colorscheme["cursor-line"]; ok { - fg, _, _ := s.Decompose() - style = style.Background(fg) - } - for i := vloc.X; i < w.Width; i++ { - screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, style) - } - } - } - - for _, c := range cursors { - if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() { - w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0) - } - } - - bloc.X = w.StartCol - bloc.Y++ - if bloc.Y >= b.LinesNum() { - break - } - } -} - -func (w *BufWindow) displayStatusLine() { - w.sline.Display() -} - -// Display displays the buffer and the statusline -func (w *BufWindow) Display() { - w.displayBuffer() - w.displayStatusLine() -} diff --git a/cmd/micro/info/infobuffer.go b/cmd/micro/info/infobuffer.go index 4e22d2f6..438e3b52 100644 --- a/cmd/micro/info/infobuffer.go +++ b/cmd/micro/info/infobuffer.go @@ -28,7 +28,7 @@ type InfoBuf struct { HistoryNum int // Is the current message a message from the gutter - GutterMessage bool + HasGutter bool PromptCallback func(resp string, canceled bool) EventCallback func(resp string) @@ -63,6 +63,18 @@ func (i *InfoBuf) Message(msg ...interface{}) { } } +// GutterMessage displays a message and marks it as a gutter message +func (i *InfoBuf) GutterMessage(msg ...interface{}) { + i.Message(msg...) + i.HasGutter = true +} + +// ClearGutter clears the info bar and unmarks the message +func (i *InfoBuf) ClearGutter() { + i.HasGutter = false + i.Message("") +} + // Error sends an error message to the user func (i *InfoBuf) Error(msg ...interface{}) { // only display a new message if there isn't an active prompt @@ -96,6 +108,7 @@ func (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(s i.Msg = prompt i.HasPrompt = true i.HasMessage, i.HasError, i.HasYN = false, false, false + i.HasGutter = false i.PromptCallback = donecb i.EventCallback = eventcb i.Buffer.Insert(i.Buffer.Start(), msg) @@ -110,6 +123,7 @@ func (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) { i.HasPrompt = true i.HasYN = true i.HasMessage, i.HasError = false, false + i.HasGutter = false i.YNCallback = donecb } @@ -118,6 +132,7 @@ func (i *InfoBuf) DonePrompt(canceled bool) { hadYN := i.HasYN i.HasPrompt = false i.HasYN = false + i.HasGutter = false if i.PromptCallback != nil && !hadYN { if canceled { i.PromptCallback("", true) @@ -143,4 +158,5 @@ func (i *InfoBuf) DonePrompt(canceled bool) { func (i *InfoBuf) Reset() { i.Msg = "" i.HasPrompt, i.HasMessage, i.HasError = false, false, false + i.HasGutter = false }