]> git.lizzy.rs Git - micro.git/blob - internal/display/statusline.go
947a3ee7b48b828dce783829f0367ac6f8ddfa2f
[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 }
51
52 func SetStatusInfoFnLua(fn string) {
53         luaFn := strings.Split(fn, ".")
54         if len(luaFn) <= 1 {
55                 return
56         }
57         plName, plFn := luaFn[0], luaFn[1]
58         pl := config.FindPlugin(plName)
59         if pl == nil {
60                 return
61         }
62         statusInfo[fn] = func(b *buffer.Buffer) string {
63                 if pl == nil || !pl.IsEnabled() {
64                         return ""
65                 }
66                 val, err := pl.Call(plFn, luar.New(ulua.L, b))
67                 if err == nil {
68                         if v, ok := val.(lua.LString); !ok {
69                                 screen.TermMessage(plFn, "should return a string")
70                                 return ""
71                         } else {
72                                 return string(v)
73                         }
74                 }
75                 return ""
76         }
77 }
78
79 // NewStatusLine returns a statusline bound to a window
80 func NewStatusLine(win *BufWindow) *StatusLine {
81         s := new(StatusLine)
82         s.win = win
83         return s
84 }
85
86 // FindOpt finds a given option in the current buffer's settings
87 func (s *StatusLine) FindOpt(opt string) interface{} {
88         if val, ok := s.win.Buf.Settings[opt]; ok {
89                 return val
90         }
91         return "null"
92 }
93
94 var formatParser = regexp.MustCompile(`\$\(.+?\)`)
95
96 // Display draws the statusline to the screen
97 func (s *StatusLine) Display() {
98         // We'll draw the line at the lowest line in the window
99         y := s.win.Height + s.win.Y - 1
100
101         winX := s.win.X
102
103         b := s.win.Buf
104         // autocomplete suggestions (for the buffer, not for the infowindow)
105         if b.HasSuggestions && len(b.Suggestions) > 1 {
106                 statusLineStyle := config.DefStyle.Reverse(true)
107                 if style, ok := config.Colorscheme["statusline"]; ok {
108                         statusLineStyle = style
109                 }
110                 x := 0
111                 for j, sug := range b.Suggestions {
112                         style := statusLineStyle
113                         if b.CurSuggestion == j {
114                                 style = style.Reverse(true)
115                         }
116                         for _, r := range sug {
117                                 screen.SetContent(winX+x, y, r, nil, style)
118                                 x++
119                                 if x >= s.win.Width {
120                                         return
121                                 }
122                         }
123                         screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
124                         x++
125                         if x >= s.win.Width {
126                                 return
127                         }
128                 }
129
130                 for x < s.win.Width {
131                         screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
132                         x++
133                 }
134                 return
135         }
136
137         formatter := func(match []byte) []byte {
138                 name := match[2 : len(match)-1]
139                 if bytes.HasPrefix(name, []byte("opt")) {
140                         option := name[4:]
141                         return []byte(fmt.Sprint(s.FindOpt(string(option))))
142                 } else if bytes.HasPrefix(name, []byte("bind")) {
143                         binding := string(name[5:])
144                         for k, v := range config.Bindings["buffer"] {
145                                 if v == binding {
146                                         return []byte(k)
147                                 }
148                         }
149                         return []byte("null")
150                 } else {
151                         if fn, ok := statusInfo[string(name)]; ok {
152                                 return []byte(fn(s.win.Buf))
153                         }
154                         return []byte{}
155                 }
156         }
157
158         leftText := []byte(s.win.Buf.Settings["statusformatl"].(string))
159         leftText = formatParser.ReplaceAllFunc(leftText, formatter)
160         rightText := []byte(s.win.Buf.Settings["statusformatr"].(string))
161         rightText = formatParser.ReplaceAllFunc(rightText, formatter)
162
163         statusLineStyle := config.DefStyle.Reverse(true)
164         if style, ok := config.Colorscheme["statusline"]; ok {
165                 statusLineStyle = style
166         }
167
168         leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
169         rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
170
171         for x := 0; x < s.win.Width; x++ {
172                 if x < leftLen {
173                         r, combc, size := util.DecodeCharacter(leftText)
174                         leftText = leftText[size:]
175                         rw := runewidth.RuneWidth(r)
176                         for j := 0; j < rw; j++ {
177                                 c := r
178                                 if j > 0 {
179                                         c = ' '
180                                         combc = nil
181                                         x++
182                                 }
183                                 screen.SetContent(winX+x, y, c, combc, statusLineStyle)
184                         }
185                 } else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
186                         r, combc, size := util.DecodeCharacter(rightText)
187                         rightText = rightText[size:]
188                         rw := runewidth.RuneWidth(r)
189                         for j := 0; j < rw; j++ {
190                                 c := r
191                                 if j > 0 {
192                                         c = ' '
193                                         combc = nil
194                                         x++
195                                 }
196                                 screen.SetContent(winX+x, y, c, combc, statusLineStyle)
197                         }
198                 } else {
199                         screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
200                 }
201         }
202 }