]> git.lizzy.rs Git - micro.git/blob - internal/display/infowindow.go
Use abspath for local glob settings
[micro.git] / internal / display / infowindow.go
1 package display
2
3 import (
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"
11 )
12
13 type InfoWindow struct {
14         *info.InfoBuf
15         *View
16
17         hscroll int
18 }
19
20 func (i *InfoWindow) errStyle() tcell.Style {
21         errStyle := config.DefStyle.
22                 Foreground(tcell.ColorBlack).
23                 Background(tcell.ColorMaroon)
24
25         if _, ok := config.Colorscheme["error-message"]; ok {
26                 errStyle = config.Colorscheme["error-message"]
27         }
28
29         return errStyle
30 }
31
32 func (i *InfoWindow) defStyle() tcell.Style {
33         defStyle := config.DefStyle
34
35         if _, ok := config.Colorscheme["message"]; ok {
36                 defStyle = config.Colorscheme["message"]
37         }
38
39         return defStyle
40 }
41
42 func NewInfoWindow(b *info.InfoBuf) *InfoWindow {
43         iw := new(InfoWindow)
44         iw.InfoBuf = b
45         iw.View = new(View)
46
47         iw.Width, iw.Y = screen.Screen.Size()
48         iw.Y--
49
50         return iw
51 }
52
53 func (i *InfoWindow) Resize(w, h int) {
54         i.Width = w
55         i.Y = h
56 }
57
58 func (i *InfoWindow) SetBuffer(b *buffer.Buffer) {
59         i.InfoBuf.Buffer = b
60 }
61
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 }
67
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}
73 }
74
75 func (i *InfoWindow) BufView() View {
76         return View{
77                 X:         0,
78                 Y:         i.Y,
79                 Width:     i.Width,
80                 Height:    1,
81                 StartLine: SLoc{0, 0},
82                 StartCol:  0,
83         }
84 }
85
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} }
91
92 func (i *InfoWindow) Clear() {
93         for x := 0; x < i.Width; x++ {
94                 screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
95         }
96 }
97
98 func (i *InfoWindow) displayBuffer() {
99         b := i.Buffer
100         line := b.LineBytes(0)
101         activeC := b.GetActiveCursor()
102
103         blocX := 0
104         vlocX := util.CharacterCountInString(i.Msg)
105
106         tabsize := 4
107         line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
108         blocX = bslice
109
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)
118
119                                 if s, ok := config.Colorscheme["selection"]; ok {
120                                         style = s
121                                 }
122
123                         }
124
125                         rw := runewidth.RuneWidth(r)
126                         for j := 0; j < rw; j++ {
127                                 c := r
128                                 if j > 0 {
129                                         c = ' '
130                                         combc = nil
131                                 }
132                                 screen.SetContent(vlocX, i.Y, c, combc, style)
133                         }
134                         vlocX++
135                 }
136                 nColsBeforeStart--
137         }
138
139         totalwidth := blocX - nColsBeforeStart
140         for len(line) > 0 {
141                 curVX := vlocX
142                 curBX := blocX
143                 r, combc, size := util.DecodeCharacter(line)
144
145                 draw(r, combc, i.defStyle())
146
147                 width := 0
148
149                 char := ' '
150                 switch r {
151                 case '\t':
152                         ts := tabsize - (totalwidth % tabsize)
153                         width = ts
154                 default:
155                         width = runewidth.RuneWidth(r)
156                         char = '@'
157                 }
158
159                 blocX++
160                 line = line[size:]
161
162                 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
163                 if width > 1 {
164                         for j := 1; j < width; j++ {
165                                 draw(char, nil, i.defStyle())
166                         }
167                 }
168                 if activeC.X == curBX {
169                         screen.ShowCursor(curVX, i.Y)
170                 }
171                 totalwidth += width
172                 if vlocX >= i.Width {
173                         break
174                 }
175         }
176         if activeC.X == blocX {
177                 screen.ShowCursor(vlocX, i.Y)
178         }
179 }
180
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"}
182
183 func (i *InfoWindow) displayKeyMenu() {
184         // TODO: maybe make this based on the actual keybindings
185
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())
190                         } else {
191                                 screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
192                         }
193                 }
194         }
195 }
196
197 func (i *InfoWindow) totalSize() int {
198         sum := 0
199         for _, n := range i.Suggestions {
200                 sum += runewidth.StringWidth(n) + 1
201         }
202         return sum
203 }
204
205 func (i *InfoWindow) scrollToSuggestion() {
206         x := 0
207         s := i.totalSize()
208
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)
216                         }
217                         break
218                 }
219                 x += c + 1
220         }
221
222         if s-i.Width <= 0 {
223                 i.hscroll = 0
224         }
225 }
226
227 func (i *InfoWindow) Display() {
228         if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
229                 i.Clear()
230                 x := 0
231                 if config.GetGlobalOption("keymenu").(bool) {
232                         i.displayKeyMenu()
233                 }
234
235                 if !i.HasPrompt && !i.HasMessage && !i.HasError {
236                         return
237                 }
238                 i.Clear()
239                 style := i.defStyle()
240
241                 if i.HasError {
242                         style = i.errStyle()
243                 }
244
245                 display := i.Msg
246                 for _, c := range display {
247                         screen.SetContent(x, i.Y, c, nil, style)
248                         x += runewidth.RuneWidth(c)
249                 }
250
251                 if i.HasPrompt {
252                         i.displayBuffer()
253                 }
254         }
255
256         if i.HasSuggestions && len(i.Suggestions) > 1 {
257                 i.scrollToSuggestion()
258
259                 x := -i.hscroll
260                 done := false
261
262                 statusLineStyle := config.DefStyle.Reverse(true)
263                 if style, ok := config.Colorscheme["statusline"]; ok {
264                         statusLineStyle = style
265                 }
266                 keymenuOffset := 0
267                 if config.GetGlobalOption("keymenu").(bool) {
268                         keymenuOffset = len(keydisplay)
269                 }
270
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++ {
275                                 c := r
276                                 if j > 0 {
277                                         c = ' '
278                                 }
279
280                                 if x == i.Width-1 && !done {
281                                         screen.SetContent(i.Width-1, y, '>', nil, s)
282                                         x++
283                                         break
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)
288                                 }
289                                 x++
290                         }
291                 }
292
293                 for j, s := range i.Suggestions {
294                         style := statusLineStyle
295                         if i.CurSuggestion == j {
296                                 style = style.Reverse(true)
297                         }
298                         for _, r := range s {
299                                 draw(r, style)
300                                 // screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
301                         }
302                         draw(' ', statusLineStyle)
303                 }
304
305                 for x < i.Width {
306                         draw(' ', statusLineStyle)
307                 }
308         }
309 }