]> git.lizzy.rs Git - micro.git/blob - internal/display/statusline.go
better top
[micro.git] / internal / display / statusline.go
1 package display
2
3 import (
4         "bytes"
5         "fmt"
6         "path"
7         "regexp"
8         "strconv"
9         "strings"
10         "unicode/utf8"
11
12         luar "layeh.com/gopher-luar"
13
14         runewidth "github.com/mattn/go-runewidth"
15         lua "github.com/yuin/gopher-lua"
16         "github.com/zyedidia/micro/internal/buffer"
17         "github.com/zyedidia/micro/internal/config"
18         ulua "github.com/zyedidia/micro/internal/lua"
19         "github.com/zyedidia/micro/internal/screen"
20         "github.com/zyedidia/micro/internal/util"
21 )
22
23 // StatusLine represents the information line at the bottom
24 // of each window
25 // It gives information such as filename, whether the file has been
26 // modified, filetype, cursor location
27 type StatusLine struct {
28         Info map[string]func(*buffer.Buffer) string
29
30         win *BufWindow
31 }
32
33 var statusInfo = map[string]func(*buffer.Buffer) string{
34         "filename": func(b *buffer.Buffer) string {
35                 if b.Settings["basename"].(bool) {
36                         return path.Base(b.GetName())
37                 }
38                 return b.GetName()
39         },
40         "line": func(b *buffer.Buffer) string {
41                 return strconv.Itoa(b.GetActiveCursor().Y + 1)
42         },
43         "col": func(b *buffer.Buffer) string {
44                 return strconv.Itoa(b.GetActiveCursor().X + 1)
45         },
46         "modified": func(b *buffer.Buffer) string {
47                 if b.Modified() {
48                         return "+ "
49                 }
50                 if b.Type.Readonly {
51                         return "[ro] "
52                 }
53                 return ""
54         },
55 }
56
57 func SetStatusInfoFnLua(fn string) {
58         luaFn := strings.Split(fn, ".")
59         if len(luaFn) <= 1 {
60                 return
61         }
62         plName, plFn := luaFn[0], luaFn[1]
63         pl := config.FindPlugin(plName)
64         if pl == nil {
65                 return
66         }
67         statusInfo[fn] = func(b *buffer.Buffer) string {
68                 if pl == nil || !pl.IsEnabled() {
69                         return ""
70                 }
71                 val, err := pl.Call(plFn, luar.New(ulua.L, b))
72                 if err == nil {
73                         if v, ok := val.(lua.LString); !ok {
74                                 screen.TermMessage(plFn, "should return a string")
75                                 return ""
76                         } else {
77                                 return string(v)
78                         }
79                 }
80                 return ""
81         }
82 }
83
84 // NewStatusLine returns a statusline bound to a window
85 func NewStatusLine(win *BufWindow) *StatusLine {
86         s := new(StatusLine)
87         s.win = win
88         return s
89 }
90
91 // FindOpt finds a given option in the current buffer's settings
92 func (s *StatusLine) FindOpt(opt string) interface{} {
93         if val, ok := s.win.Buf.Settings[opt]; ok {
94                 return val
95         }
96         return "null"
97 }
98
99 var formatParser = regexp.MustCompile(`\$\(.+?\)`)
100
101 // Display draws the statusline to the screen
102 func (s *StatusLine) Display() {
103         // We'll draw the line at the lowest line in the window
104         y := s.win.Height + s.win.Y - 1
105
106         b := s.win.Buf
107         // autocomplete suggestions (for the buffer, not for the infowindow)
108         if b.HasSuggestions && len(b.Suggestions) > 1 {
109                 statusLineStyle := config.DefStyle.Reverse(true)
110                 if style, ok := config.Colorscheme["statusline"]; ok {
111                         statusLineStyle = style
112                 }
113                 keymenuOffset := 0
114                 if config.GetGlobalOption("keymenu").(bool) {
115                         keymenuOffset = len(keydisplay)
116                 }
117                 x := 0
118                 for j, sug := range b.Suggestions {
119                         style := statusLineStyle
120                         if b.CurSuggestion == j {
121                                 style = style.Reverse(true)
122                         }
123                         for _, r := range sug {
124                                 screen.SetContent(x, y-keymenuOffset, r, nil, style)
125                                 x++
126                                 if x >= s.win.Width {
127                                         return
128                                 }
129                         }
130                         screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
131                         x++
132                         if x >= s.win.Width {
133                                 return
134                         }
135                 }
136
137                 for x < s.win.Width {
138                         screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
139                         x++
140                 }
141                 return
142         }
143
144         formatter := func(match []byte) []byte {
145                 name := match[2 : len(match)-1]
146                 if bytes.HasPrefix(name, []byte("opt")) {
147                         option := name[4:]
148                         return []byte(fmt.Sprint(s.FindOpt(string(option))))
149                 } else if bytes.HasPrefix(name, []byte("bind")) {
150                         binding := string(name[5:])
151                         for k, v := range config.Bindings {
152                                 if v == binding {
153                                         return []byte(k)
154                                 }
155                         }
156                         return []byte("null")
157                 } else {
158                         if fn, ok := statusInfo[string(name)]; ok {
159                                 return []byte(fn(s.win.Buf))
160                         }
161                         return []byte{}
162                 }
163         }
164
165         leftText := []byte(s.win.Buf.Settings["statusformatl"].(string))
166         leftText = formatParser.ReplaceAllFunc(leftText, formatter)
167         rightText := []byte(s.win.Buf.Settings["statusformatr"].(string))
168         rightText = formatParser.ReplaceAllFunc(rightText, formatter)
169
170         statusLineStyle := config.DefStyle.Reverse(true)
171         if style, ok := config.Colorscheme["statusline"]; ok {
172                 statusLineStyle = style
173         }
174
175         leftLen := util.StringWidth(leftText, utf8.RuneCount(leftText), 1)
176         rightLen := util.StringWidth(rightText, utf8.RuneCount(rightText), 1)
177
178         winX := s.win.X
179         for x := 0; x < s.win.Width; x++ {
180                 if x < leftLen {
181                         r, size := utf8.DecodeRune(leftText)
182                         leftText = leftText[size:]
183                         rw := runewidth.RuneWidth(r)
184                         for j := 0; j < rw; j++ {
185                                 c := r
186                                 if j > 0 {
187                                         c = ' '
188                                         x++
189                                 }
190                                 screen.SetContent(winX+x, y, c, nil, statusLineStyle)
191                         }
192                 } else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
193                         r, size := utf8.DecodeRune(rightText)
194                         rightText = rightText[size:]
195                         rw := runewidth.RuneWidth(r)
196                         for j := 0; j < rw; j++ {
197                                 c := r
198                                 if j > 0 {
199                                         c = ' '
200                                         x++
201                                 }
202                                 screen.SetContent(winX+x, y, c, nil, statusLineStyle)
203                         }
204                 } else {
205                         screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
206                 }
207         }
208 }