]> git.lizzy.rs Git - micro.git/blob - cmd/micro/tab.go
Code optimisation (#1117)
[micro.git] / cmd / micro / tab.go
1 package main
2
3 import (
4         "sort"
5
6         "github.com/zyedidia/tcell"
7 )
8
9 var tabBarOffset int
10
11 // A Tab holds an array of views and a splitTree to determine how the
12 // views should be arranged
13 type Tab struct {
14         // This contains all the views in this tab
15         // There is generally only one view per tab, but you can have
16         // multiple views with splits
17         Views []*View
18         // This is the current view for this tab
19         CurView int
20
21         tree *SplitTree
22 }
23
24 // NewTabFromView creates a new tab and puts the given view in the tab
25 func NewTabFromView(v *View) *Tab {
26         t := new(Tab)
27         t.Views = append(t.Views, v)
28         t.Views[0].Num = 0
29
30         t.tree = new(SplitTree)
31         t.tree.kind = VerticalSplit
32         t.tree.children = []Node{NewLeafNode(t.Views[0], t.tree)}
33
34         w, h := screen.Size()
35         t.tree.width = w
36         t.tree.height = h
37
38         if globalSettings["infobar"].(bool) {
39                 t.tree.height--
40         }
41         if globalSettings["keymenu"].(bool) {
42                 t.tree.height -= 2
43         }
44
45         t.Resize()
46
47         return t
48 }
49
50 // SetNum sets all this tab's views to have the correct tab number
51 func (t *Tab) SetNum(num int) {
52         t.tree.tabNum = num
53         for _, v := range t.Views {
54                 v.TabNum = num
55         }
56 }
57
58 // Cleanup cleans up the tree (for example if views have closed)
59 func (t *Tab) Cleanup() {
60         t.tree.Cleanup()
61 }
62
63 // Resize handles a resize event from the terminal and resizes
64 // all child views correctly
65 func (t *Tab) Resize() {
66         w, h := screen.Size()
67         t.tree.width = w
68         t.tree.height = h
69
70         if globalSettings["infobar"].(bool) {
71                 t.tree.height--
72         }
73         if globalSettings["keymenu"].(bool) {
74                 t.tree.height -= 2
75         }
76
77         t.tree.ResizeSplits()
78
79         for i, v := range t.Views {
80                 v.Num = i
81                 if v.Type == vtTerm {
82                         v.term.Resize(v.Width, v.Height)
83                 }
84         }
85 }
86
87 // CurView returns the current view
88 func CurView() *View {
89         curTab := tabs[curTab]
90         return curTab.Views[curTab.CurView]
91 }
92
93 // TabbarString returns the string that should be displayed in the tabbar
94 // It also returns a map containing which indicies correspond to which tab number
95 // This is useful when we know that the mouse click has occurred at an x location
96 // but need to know which tab that corresponds to to accurately change the tab
97 func TabbarString() (string, map[int]int) {
98         str := ""
99         indicies := make(map[int]int)
100         for i, t := range tabs {
101                 if i == curTab {
102                         str += "["
103                 } else {
104                         str += " "
105                 }
106                 buf := t.Views[t.CurView].Buf
107                 str += buf.GetName()
108                 if buf.Modified() {
109                         str += " +"
110                 }
111                 if i == curTab {
112                         str += "]"
113                 } else {
114                         str += " "
115                 }
116                 indicies[Count(str)-1] = i + 1
117                 str += " "
118         }
119         return str, indicies
120 }
121
122 // TabbarHandleMouseEvent checks the given mouse event if it is clicking on the tabbar
123 // If it is it changes the current tab accordingly
124 // This function returns true if the tab is changed
125 func TabbarHandleMouseEvent(event tcell.Event) bool {
126         // There is no tabbar displayed if there are less than 2 tabs
127         if len(tabs) <= 1 {
128                 return false
129         }
130
131         switch e := event.(type) {
132         case *tcell.EventMouse:
133                 button := e.Buttons()
134                 // Must be a left click
135                 if button == tcell.Button1 {
136                         x, y := e.Position()
137                         if y != 0 {
138                                 return false
139                         }
140                         str, indicies := TabbarString()
141                         if x+tabBarOffset >= len(str) {
142                                 return false
143                         }
144                         var tabnum int
145                         var keys []int
146                         for k := range indicies {
147                                 keys = append(keys, k)
148                         }
149                         sort.Ints(keys)
150                         for _, k := range keys {
151                                 if x+tabBarOffset <= k {
152                                         tabnum = indicies[k] - 1
153                                         break
154                                 }
155                         }
156                         curTab = tabnum
157                         return true
158                 }
159         }
160
161         return false
162 }
163
164 // DisplayTabs displays the tabbar at the top of the editor if there are multiple tabs
165 func DisplayTabs() {
166         if len(tabs) <= 1 {
167                 return
168         }
169
170         str, indicies := TabbarString()
171
172         tabBarStyle := defStyle.Reverse(true)
173         if style, ok := colorscheme["tabbar"]; ok {
174                 tabBarStyle = style
175         }
176
177         // Maybe there is a unicode filename?
178         fileRunes := []rune(str)
179         w, _ := screen.Size()
180         tooWide := (w < len(fileRunes))
181
182         // if the entire tab-bar is longer than the screen is wide,
183         // then it should be truncated appropriately to keep the
184         // active tab visible on the UI.
185         if tooWide == true {
186                 // first we have to work out where the selected tab is
187                 // out of the total length of the tab bar. this is done
188                 // by extracting the hit-areas from the indicies map
189                 // that was constructed by `TabbarString()`
190                 var keys []int
191                 for offset := range indicies {
192                         keys = append(keys, offset)
193                 }
194                 // sort them to be in ascending order so that values will
195                 // correctly reflect the displayed ordering of the tabs
196                 sort.Ints(keys)
197                 // record the offset of each tab and the previous tab so
198                 // we can find the position of the tab's hit-box.
199                 previousTabOffset := 0
200                 currentTabOffset := 0
201                 for _, k := range keys {
202                         tabIndex := indicies[k] - 1
203                         if tabIndex == curTab {
204                                 currentTabOffset = k
205                                 break
206                         }
207                         // this is +2 because there are two padding spaces that aren't accounted
208                         // for in the display. please note that this is for cosmetic purposes only.
209                         previousTabOffset = k + 2
210                 }
211                 // get the width of the hitbox of the active tab, from there calculate the offsets
212                 // to the left and right of it to approximately center it on the tab bar display.
213                 centeringOffset := (w - (currentTabOffset - previousTabOffset))
214                 leftBuffer := previousTabOffset - (centeringOffset / 2)
215                 rightBuffer := currentTabOffset + (centeringOffset / 2)
216
217                 // check to make sure we haven't overshot the bounds of the string,
218                 // if we have, then take that remainder and put it on the left side
219                 overshotRight := rightBuffer - len(fileRunes)
220                 if overshotRight > 0 {
221                         leftBuffer = leftBuffer + overshotRight
222                 }
223
224                 overshotLeft := leftBuffer - 0
225                 if overshotLeft < 0 {
226                         leftBuffer = 0
227                         rightBuffer = leftBuffer + (w - 1)
228                 } else {
229                         rightBuffer = leftBuffer + (w - 2)
230                 }
231
232                 if rightBuffer > len(fileRunes)-1 {
233                         rightBuffer = len(fileRunes) - 1
234                 }
235
236                 // construct a new buffer of text to put the
237                 // newly formatted tab bar text into.
238                 var displayText []rune
239
240                 // if the left-side of the tab bar isn't at the start
241                 // of the constructed tab bar text, then show that are
242                 // more tabs to the left by displaying a "+"
243                 if leftBuffer != 0 {
244                         displayText = append(displayText, '+')
245                 }
246                 // copy the runes in from the original tab bar text string
247                 // into the new display buffer
248                 for x := leftBuffer; x < rightBuffer; x++ {
249                         displayText = append(displayText, fileRunes[x])
250                 }
251                 // if there is more text to the right of the right-most
252                 // column in the tab bar text, then indicate there are more
253                 // tabs to the right by displaying a "+"
254                 if rightBuffer < len(fileRunes)-1 {
255                         displayText = append(displayText, '+')
256                 }
257
258                 // now store the offset from zero of the left-most text
259                 // that is being displayed. This is to ensure that when
260                 // clicking on the tab bar, the correct tab gets selected.
261                 tabBarOffset = leftBuffer
262
263                 // use the constructed buffer as the display buffer to print
264                 // onscreen.
265                 fileRunes = displayText
266         } else {
267                 tabBarOffset = 0
268         }
269
270         // iterate over the width of the terminal display and for each column,
271         // write a character into the tab display area with the appropriate style.
272         for x := 0; x < w; x++ {
273                 if x < len(fileRunes) {
274                         screen.SetContent(x, 0, fileRunes[x], nil, tabBarStyle)
275                 } else {
276                         screen.SetContent(x, 0, ' ', nil, tabBarStyle)
277                 }
278         }
279 }