]> git.lizzy.rs Git - micro.git/blob - internal/display/infowindow.go
Fix makefile tags dependencies
[micro.git] / internal / display / infowindow.go
1 package display
2
3 import (
4         "unicode/utf8"
5
6         runewidth "github.com/mattn/go-runewidth"
7         "github.com/zyedidia/micro/internal/buffer"
8         "github.com/zyedidia/micro/internal/config"
9         "github.com/zyedidia/micro/internal/info"
10         "github.com/zyedidia/micro/internal/screen"
11         "github.com/zyedidia/micro/internal/util"
12         "github.com/zyedidia/tcell"
13 )
14
15 type InfoWindow struct {
16         *info.InfoBuf
17         *View
18
19         hscroll int
20 }
21
22 func (i *InfoWindow) errStyle() tcell.Style {
23         errStyle := config.DefStyle.
24                 Foreground(tcell.ColorBlack).
25                 Background(tcell.ColorMaroon)
26
27         if _, ok := config.Colorscheme["error-message"]; ok {
28                 errStyle = config.Colorscheme["error-message"]
29         }
30
31         return errStyle
32 }
33
34 func (i *InfoWindow) defStyle() tcell.Style {
35         defStyle := config.DefStyle
36
37         if _, ok := config.Colorscheme["message"]; ok {
38                 defStyle = config.Colorscheme["message"]
39         }
40
41         return defStyle
42 }
43
44 func NewInfoWindow(b *info.InfoBuf) *InfoWindow {
45         iw := new(InfoWindow)
46         iw.InfoBuf = b
47         iw.View = new(View)
48
49         iw.Width, iw.Y = screen.Screen.Size()
50         iw.Y--
51
52         return iw
53 }
54
55 func (i *InfoWindow) Resize(w, h int) {
56         i.Width = w
57         i.Y = h
58 }
59
60 func (i *InfoWindow) SetBuffer(b *buffer.Buffer) {
61         i.InfoBuf.Buffer = b
62 }
63
64 func (i *InfoWindow) Relocate() bool   { return false }
65 func (i *InfoWindow) GetView() *View   { return i.View }
66 func (i *InfoWindow) SetView(v *View)  {}
67 func (i *InfoWindow) SetActive(b bool) {}
68 func (i *InfoWindow) IsActive() bool   { return true }
69
70 func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
71         c := i.Buffer.GetActiveCursor()
72         l := i.Buffer.LineBytes(0)
73         n := utf8.RuneCountInString(i.Msg)
74         return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
75 }
76
77 func (i *InfoWindow) Clear() {
78         for x := 0; x < i.Width; x++ {
79                 screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
80         }
81 }
82
83 func (i *InfoWindow) displayBuffer() {
84         b := i.Buffer
85         line := b.LineBytes(0)
86         activeC := b.GetActiveCursor()
87
88         blocX := 0
89         vlocX := utf8.RuneCountInString(i.Msg)
90
91         tabsize := 4
92         line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
93         blocX = bslice
94
95         draw := func(r rune, style tcell.Style) {
96                 if nColsBeforeStart <= 0 {
97                         bloc := buffer.Loc{X: blocX, Y: 0}
98                         if activeC.HasSelection() &&
99                                 (bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) ||
100                                         bloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) {
101                                 // The current character is selected
102                                 style = i.defStyle().Reverse(true)
103
104                                 if s, ok := config.Colorscheme["selection"]; ok {
105                                         style = s
106                                 }
107
108                         }
109
110                         rw := runewidth.RuneWidth(r)
111                         for j := 0; j < rw; j++ {
112                                 c := r
113                                 if j > 0 {
114                                         c = ' '
115                                 }
116                                 screen.SetContent(vlocX, i.Y, c, nil, style)
117                         }
118                         vlocX++
119                 }
120                 nColsBeforeStart--
121         }
122
123         totalwidth := blocX - nColsBeforeStart
124         for len(line) > 0 {
125                 curVX := vlocX
126                 curBX := blocX
127                 r, size := utf8.DecodeRune(line)
128
129                 draw(r, i.defStyle())
130
131                 width := 0
132
133                 char := ' '
134                 switch r {
135                 case '\t':
136                         ts := tabsize - (totalwidth % tabsize)
137                         width = ts
138                 default:
139                         width = runewidth.RuneWidth(r)
140                         char = '@'
141                 }
142
143                 blocX++
144                 line = line[size:]
145
146                 // Draw any extra characters either spaces for tabs or @ for incomplete wide runes
147                 if width > 1 {
148                         for j := 1; j < width; j++ {
149                                 draw(char, i.defStyle())
150                         }
151                 }
152                 if activeC.X == curBX {
153                         screen.ShowCursor(curVX, i.Y)
154                 }
155                 totalwidth += width
156                 if vlocX >= i.Width {
157                         break
158                 }
159         }
160         if activeC.X == blocX {
161                 screen.ShowCursor(vlocX, i.Y)
162         }
163 }
164
165 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"}
166
167 func (i *InfoWindow) displayKeyMenu() {
168         // TODO: maybe make this based on the actual keybindings
169
170         for y := 0; y < len(keydisplay); y++ {
171                 for x := 0; x < i.Width; x++ {
172                         if x < len(keydisplay[y]) {
173                                 screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
174                         } else {
175                                 screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
176                         }
177                 }
178         }
179 }
180
181 func (i *InfoWindow) totalSize() int {
182         sum := 0
183         for _, n := range i.Suggestions {
184                 sum += runewidth.StringWidth(n) + 1
185         }
186         return sum
187 }
188
189 func (i *InfoWindow) scrollToSuggestion() {
190         x := 0
191         s := i.totalSize()
192
193         for j, n := range i.Suggestions {
194                 c := utf8.RuneCountInString(n)
195                 if j == i.CurSuggestion {
196                         if x+c >= i.hscroll+i.Width {
197                                 i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
198                         } else if x < i.hscroll {
199                                 i.hscroll = util.Clamp(x-1, 0, s-i.Width)
200                         }
201                         break
202                 }
203                 x += c + 1
204         }
205
206         if s-i.Width <= 0 {
207                 i.hscroll = 0
208         }
209 }
210
211 func (i *InfoWindow) Display() {
212         i.Clear()
213         x := 0
214         if config.GetGlobalOption("keymenu").(bool) {
215                 i.displayKeyMenu()
216         }
217
218         if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
219                 if !i.HasPrompt && !i.HasMessage && !i.HasError {
220                         return
221                 }
222                 i.Clear()
223                 style := i.defStyle()
224
225                 if i.HasError {
226                         style = i.errStyle()
227                 }
228
229                 display := i.Msg
230                 for _, c := range display {
231                         screen.SetContent(x, i.Y, c, nil, style)
232                         x += runewidth.RuneWidth(c)
233                 }
234
235                 if i.HasPrompt {
236                         i.displayBuffer()
237                 }
238         }
239
240         if i.HasSuggestions && len(i.Suggestions) > 1 {
241                 i.scrollToSuggestion()
242
243                 x := -i.hscroll
244                 done := false
245
246                 statusLineStyle := config.DefStyle.Reverse(true)
247                 if style, ok := config.Colorscheme["statusline"]; ok {
248                         statusLineStyle = style
249                 }
250                 keymenuOffset := 0
251                 if config.GetGlobalOption("keymenu").(bool) {
252                         keymenuOffset = len(keydisplay)
253                 }
254
255                 draw := func(r rune, s tcell.Style) {
256                         y := i.Y - keymenuOffset - 1
257                         rw := runewidth.RuneWidth(r)
258                         for j := 0; j < rw; j++ {
259                                 c := r
260                                 if j > 0 {
261                                         c = ' '
262                                 }
263
264                                 if x == i.Width-1 && !done {
265                                         screen.SetContent(i.Width-1, y, '>', nil, s)
266                                         x++
267                                         break
268                                 } else if x == 0 && i.hscroll > 0 {
269                                         screen.SetContent(0, y, '<', nil, s)
270                                 } else if x >= 0 && x < i.Width {
271                                         screen.SetContent(x, y, c, nil, s)
272                                 }
273                                 x++
274                         }
275                 }
276
277                 for j, s := range i.Suggestions {
278                         style := statusLineStyle
279                         if i.CurSuggestion == j {
280                                 style = style.Reverse(true)
281                         }
282                         for _, r := range s {
283                                 draw(r, style)
284                                 // screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
285                         }
286                         draw(' ', statusLineStyle)
287                 }
288
289                 for x < i.Width {
290                         draw(' ', statusLineStyle)
291                 }
292         }
293 }