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