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