]> git.lizzy.rs Git - micro.git/blob - internal/action/tab.go
Fix cursor position change after CopyLine command (#2353)
[micro.git] / internal / action / tab.go
1 package action
2
3 import (
4         "github.com/zyedidia/micro/v2/internal/buffer"
5         "github.com/zyedidia/micro/v2/internal/config"
6         "github.com/zyedidia/micro/v2/internal/display"
7         "github.com/zyedidia/micro/v2/internal/screen"
8         "github.com/zyedidia/micro/v2/internal/views"
9         "github.com/zyedidia/tcell/v2"
10 )
11
12 // The TabList is a list of tabs and a window to display the tab bar
13 // at the top of the screen
14 type TabList struct {
15         *display.TabWindow
16         List []*Tab
17 }
18
19 // NewTabList creates a TabList from a list of buffers by creating a Tab
20 // for each buffer
21 func NewTabList(bufs []*buffer.Buffer) *TabList {
22         w, h := screen.Screen.Size()
23         iOffset := config.GetInfoBarOffset()
24         tl := new(TabList)
25         tl.List = make([]*Tab, len(bufs))
26         if len(bufs) > 1 {
27                 for i, b := range bufs {
28                         tl.List[i] = NewTabFromBuffer(0, 1, w, h-1-iOffset, b)
29                 }
30         } else {
31                 tl.List[0] = NewTabFromBuffer(0, 0, w, h-iOffset, bufs[0])
32         }
33         tl.TabWindow = display.NewTabWindow(w, 0)
34         tl.Names = make([]string, len(bufs))
35
36         return tl
37 }
38
39 // UpdateNames makes sure that the list of names the tab window has access to is
40 // correct
41 func (t *TabList) UpdateNames() {
42         t.Names = t.Names[:0]
43         for _, p := range t.List {
44                 t.Names = append(t.Names, p.Panes[p.active].Name())
45         }
46 }
47
48 // AddTab adds a new tab to this TabList
49 func (t *TabList) AddTab(p *Tab) {
50         t.List = append(t.List, p)
51         t.Resize()
52         t.UpdateNames()
53 }
54
55 // RemoveTab removes a tab with the given id from the TabList
56 func (t *TabList) RemoveTab(id uint64) {
57         for i, p := range t.List {
58                 if len(p.Panes) == 0 {
59                         continue
60                 }
61                 if p.Panes[0].ID() == id {
62                         copy(t.List[i:], t.List[i+1:])
63                         t.List[len(t.List)-1] = nil
64                         t.List = t.List[:len(t.List)-1]
65                         if t.Active() >= len(t.List) {
66                                 t.SetActive(len(t.List) - 1)
67                         }
68                         t.Resize()
69                         t.UpdateNames()
70                         return
71                 }
72         }
73 }
74
75 // Resize resizes all elements within the tab list
76 // One thing to note is that when there is only 1 tab
77 // the tab bar should not be drawn so resizing must take
78 // that into account
79 func (t *TabList) Resize() {
80         w, h := screen.Screen.Size()
81         iOffset := config.GetInfoBarOffset()
82         InfoBar.Resize(w, h-1)
83         if len(t.List) > 1 {
84                 for _, p := range t.List {
85                         p.Y = 1
86                         p.Node.Resize(w, h-1-iOffset)
87                         p.Resize()
88                 }
89         } else if len(t.List) == 1 {
90                 t.List[0].Y = 0
91                 t.List[0].Node.Resize(w, h-iOffset)
92                 t.List[0].Resize()
93         }
94         t.TabWindow.Resize(w, h)
95 }
96
97 // HandleEvent checks for a resize event or a mouse event on the tab bar
98 // otherwise it will forward the event to the currently active tab
99 func (t *TabList) HandleEvent(event tcell.Event) {
100         switch e := event.(type) {
101         case *tcell.EventResize:
102                 t.Resize()
103         case *tcell.EventMouse:
104                 mx, my := e.Position()
105                 switch e.Buttons() {
106                 case tcell.Button1:
107                         if my == t.Y && mx == 0 {
108                                 t.Scroll(-4)
109                                 return
110                         } else if my == t.Y && mx == t.Width-1 {
111                                 t.Scroll(4)
112                                 return
113                         }
114                         if len(t.List) > 1 {
115                                 ind := t.LocFromVisual(buffer.Loc{mx, my})
116                                 if ind != -1 {
117                                         t.SetActive(ind)
118                                         return
119                                 }
120                                 if my == 0 {
121                                         return
122                                 }
123                         }
124                 case tcell.WheelUp:
125                         if my == t.Y {
126                                 t.Scroll(4)
127                                 return
128                         }
129                 case tcell.WheelDown:
130                         if my == t.Y {
131                                 t.Scroll(-4)
132                                 return
133                         }
134                 }
135         }
136         t.List[t.Active()].HandleEvent(event)
137 }
138
139 // Display updates the names and then displays the tab bar
140 func (t *TabList) Display() {
141         t.UpdateNames()
142         if len(t.List) > 1 {
143                 t.TabWindow.Display()
144         }
145 }
146
147 // Tabs is the global tab list
148 var Tabs *TabList
149
150 func InitTabs(bufs []*buffer.Buffer) {
151         Tabs = NewTabList(bufs)
152 }
153
154 func MainTab() *Tab {
155         return Tabs.List[Tabs.Active()]
156 }
157
158 // A Tab represents a single tab
159 // It consists of a list of edit panes (the open buffers),
160 // a split tree (stored as just the root node), and a uiwindow
161 // to display the UI elements like the borders between splits
162 type Tab struct {
163         *views.Node
164         *display.UIWindow
165         Panes  []Pane
166         active int
167
168         resizing *views.Node // node currently being resized
169         // captures whether the mouse is released
170         release bool
171 }
172
173 // NewTabFromBuffer creates a new tab from the given buffer
174 func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
175         t := new(Tab)
176         t.Node = views.NewRoot(x, y, width, height)
177         t.UIWindow = display.NewUIWindow(t.Node)
178         t.release = true
179
180         e := NewBufPaneFromBuf(b, t)
181         e.SetID(t.ID())
182
183         t.Panes = append(t.Panes, e)
184         return t
185 }
186
187 func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
188         t := new(Tab)
189         t.Node = views.NewRoot(x, y, width, height)
190         t.UIWindow = display.NewUIWindow(t.Node)
191         t.release = true
192         pane.SetTab(t)
193         pane.SetID(t.ID())
194
195         t.Panes = append(t.Panes, pane)
196         return t
197 }
198
199 // HandleEvent takes a tcell event and usually dispatches it to the current
200 // active pane. However if the event is a resize or a mouse event where the user
201 // is interacting with the UI (resizing splits) then the event is consumed here
202 // If the event is a mouse event in a pane, that pane will become active and get
203 // the event
204 func (t *Tab) HandleEvent(event tcell.Event) {
205         switch e := event.(type) {
206         case *tcell.EventMouse:
207                 mx, my := e.Position()
208                 switch e.Buttons() {
209                 case tcell.Button1:
210                         wasReleased := t.release
211                         t.release = false
212                         if t.resizing != nil {
213                                 var size int
214                                 if t.resizing.Kind == views.STVert {
215                                         size = mx - t.resizing.X
216                                 } else {
217                                         size = my - t.resizing.Y + 1
218                                 }
219                                 t.resizing.ResizeSplit(size)
220                                 t.Resize()
221                                 return
222                         }
223
224                         if wasReleased {
225                                 t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
226                                 if t.resizing != nil {
227                                         return
228                                 }
229
230                                 for i, p := range t.Panes {
231                                         v := p.GetView()
232                                         inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
233                                         if inpane {
234                                                 t.SetActive(i)
235                                                 break
236                                         }
237                                 }
238                         }
239                 case tcell.ButtonNone:
240                         t.resizing = nil
241                         t.release = true
242                 default:
243                         for _, p := range t.Panes {
244                                 v := p.GetView()
245                                 inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
246                                 if inpane {
247                                         p.HandleEvent(event)
248                                         return
249                                 }
250                         }
251                 }
252
253         }
254         t.Panes[t.active].HandleEvent(event)
255 }
256
257 // SetActive changes the currently active pane to the specified index
258 func (t *Tab) SetActive(i int) {
259         t.active = i
260         for j, p := range t.Panes {
261                 if j == i {
262                         p.SetActive(true)
263                 } else {
264                         p.SetActive(false)
265                 }
266         }
267 }
268
269 // GetPane returns the pane with the given split index
270 func (t *Tab) GetPane(splitid uint64) int {
271         for i, p := range t.Panes {
272                 if p.ID() == splitid {
273                         return i
274                 }
275         }
276         return 0
277 }
278
279 // Remove pane removes the pane with the given index
280 func (t *Tab) RemovePane(i int) {
281         copy(t.Panes[i:], t.Panes[i+1:])
282         t.Panes[len(t.Panes)-1] = nil
283         t.Panes = t.Panes[:len(t.Panes)-1]
284 }
285
286 // Resize resizes all panes according to their corresponding split nodes
287 func (t *Tab) Resize() {
288         for _, p := range t.Panes {
289                 n := t.GetNode(p.ID())
290                 pv := p.GetView()
291                 offset := 0
292                 if n.X != 0 {
293                         offset = 1
294                 }
295                 pv.X, pv.Y = n.X+offset, n.Y
296                 p.SetView(pv)
297                 p.Resize(n.W-offset, n.H)
298         }
299 }
300
301 // CurPane returns the currently active pane
302 func (t *Tab) CurPane() *BufPane {
303         p, ok := t.Panes[t.active].(*BufPane)
304         if !ok {
305                 return nil
306         }
307         return p
308 }