4 runewidth "github.com/mattn/go-runewidth"
5 "github.com/zyedidia/micro/v2/internal/buffer"
6 "github.com/zyedidia/micro/v2/internal/config"
7 "github.com/zyedidia/micro/v2/internal/info"
8 "github.com/zyedidia/micro/v2/internal/screen"
9 "github.com/zyedidia/micro/v2/internal/util"
10 "github.com/zyedidia/tcell/v2"
13 type InfoWindow struct {
20 func (i *InfoWindow) errStyle() tcell.Style {
21 errStyle := config.DefStyle.
22 Foreground(tcell.ColorBlack).
23 Background(tcell.ColorMaroon)
25 if _, ok := config.Colorscheme["error-message"]; ok {
26 errStyle = config.Colorscheme["error-message"]
32 func (i *InfoWindow) defStyle() tcell.Style {
33 defStyle := config.DefStyle
35 if _, ok := config.Colorscheme["message"]; ok {
36 defStyle = config.Colorscheme["message"]
42 func NewInfoWindow(b *info.InfoBuf) *InfoWindow {
47 iw.Width, iw.Y = screen.Screen.Size()
53 func (i *InfoWindow) Resize(w, h int) {
58 func (i *InfoWindow) SetBuffer(b *buffer.Buffer) {
62 func (i *InfoWindow) Relocate() bool { return false }
63 func (i *InfoWindow) GetView() *View { return i.View }
64 func (i *InfoWindow) SetView(v *View) {}
65 func (i *InfoWindow) SetActive(b bool) {}
66 func (i *InfoWindow) IsActive() bool { return true }
68 func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
69 c := i.Buffer.GetActiveCursor()
70 l := i.Buffer.LineBytes(0)
71 n := util.CharacterCountInString(i.Msg)
72 return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
75 func (i *InfoWindow) BufView() View {
81 StartLine: SLoc{0, 0},
86 func (i *InfoWindow) Scroll(s SLoc, n int) SLoc { return s }
87 func (i *InfoWindow) Diff(s1, s2 SLoc) int { return 0 }
88 func (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc { return SLoc{0, 0} }
89 func (i *InfoWindow) VLocFromLoc(loc buffer.Loc) VLoc { return VLoc{SLoc{0, 0}, loc.X} }
90 func (i *InfoWindow) LocFromVLoc(vloc VLoc) buffer.Loc { return buffer.Loc{vloc.VisualX, 0} }
92 func (i *InfoWindow) Clear() {
93 for x := 0; x < i.Width; x++ {
94 screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
98 func (i *InfoWindow) displayBuffer() {
100 line := b.LineBytes(0)
101 activeC := b.GetActiveCursor()
104 vlocX := util.CharacterCountInString(i.Msg)
107 line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
110 draw := func(r rune, combc []rune, style tcell.Style) {
111 if nColsBeforeStart <= 0 {
112 bloc := buffer.Loc{X: blocX, Y: 0}
113 if activeC.HasSelection() &&
114 (bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) ||
115 bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) {
116 // The current character is selected
117 style = i.defStyle().Reverse(true)
119 if s, ok := config.Colorscheme["selection"]; ok {
125 rw := runewidth.RuneWidth(r)
126 for j := 0; j < rw; j++ {
132 screen.SetContent(vlocX, i.Y, c, combc, style)
139 totalwidth := blocX - nColsBeforeStart
143 r, combc, size := util.DecodeCharacter(line)
145 draw(r, combc, i.defStyle())
152 ts := tabsize - (totalwidth % tabsize)
155 width = runewidth.RuneWidth(r)
162 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
164 for j := 1; j < width; j++ {
165 draw(char, nil, i.defStyle())
168 if activeC.X == curBX {
169 screen.ShowCursor(curVX, i.Y)
172 if vlocX >= i.Width {
176 if activeC.X == blocX {
177 screen.ShowCursor(vlocX, i.Y)
181 var keydisplay = []string{"^Q Quit, ^S Save, ^O Open, ^G Help, ^E Command Bar, ^K Cut Line", "^F Find, ^Z Undo, ^Y Redo, ^A Select All, ^D Duplicate Line, ^T New Tab"}
183 func (i *InfoWindow) displayKeyMenu() {
184 // TODO: maybe make this based on the actual keybindings
186 for y := 0; y < len(keydisplay); y++ {
187 for x := 0; x < i.Width; x++ {
188 if x < len(keydisplay[y]) {
189 screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
191 screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
197 func (i *InfoWindow) totalSize() int {
199 for _, n := range i.Suggestions {
200 sum += runewidth.StringWidth(n) + 1
205 func (i *InfoWindow) scrollToSuggestion() {
209 for j, n := range i.Suggestions {
210 c := util.CharacterCountInString(n)
211 if j == i.CurSuggestion {
212 if x+c >= i.hscroll+i.Width {
213 i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
214 } else if x < i.hscroll {
215 i.hscroll = util.Clamp(x-1, 0, s-i.Width)
227 func (i *InfoWindow) Display() {
228 if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
231 if config.GetGlobalOption("keymenu").(bool) {
235 if !i.HasPrompt && !i.HasMessage && !i.HasError {
239 style := i.defStyle()
246 for _, c := range display {
247 screen.SetContent(x, i.Y, c, nil, style)
248 x += runewidth.RuneWidth(c)
256 if i.HasSuggestions && len(i.Suggestions) > 1 {
257 i.scrollToSuggestion()
262 statusLineStyle := config.DefStyle.Reverse(true)
263 if style, ok := config.Colorscheme["statusline"]; ok {
264 statusLineStyle = style
267 if config.GetGlobalOption("keymenu").(bool) {
268 keymenuOffset = len(keydisplay)
271 draw := func(r rune, s tcell.Style) {
272 y := i.Y - keymenuOffset - 1
273 rw := runewidth.RuneWidth(r)
274 for j := 0; j < rw; j++ {
280 if x == i.Width-1 && !done {
281 screen.SetContent(i.Width-1, y, '>', nil, s)
284 } else if x == 0 && i.hscroll > 0 {
285 screen.SetContent(0, y, '<', nil, s)
286 } else if x >= 0 && x < i.Width {
287 screen.SetContent(x, y, c, nil, s)
293 for j, s := range i.Suggestions {
294 style := statusLineStyle
295 if i.CurSuggestion == j {
296 style = style.Reverse(true)
298 for _, r := range s {
300 // screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
302 draw(' ', statusLineStyle)
306 draw(' ', statusLineStyle)