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