Something went wrong. Try again.
Fork of micro-editor/micro
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409package action
import ( luar "layeh.com/gopher-luar"
"github.com/micro-editor/micro/v2/internal/buffer" "github.com/micro-editor/micro/v2/internal/config" "github.com/micro-editor/micro/v2/internal/display" ulua "github.com/micro-editor/micro/v2/internal/lua" "github.com/micro-editor/micro/v2/internal/screen" "github.com/micro-editor/micro/v2/internal/views" "github.com/micro-editor/tcell/v2")
// The TabList is a list of tabs and a window to display the tab bar// at the top of the screentype TabList struct { *display.TabWindow List []*Tab}
// NewTabList creates a TabList from a list of buffers by creating a Tab// for each bufferfunc NewTabList(bufs []*buffer.Buffer) *TabList { w, h := screen.Screen.Size() iOffset := config.GetInfoBarOffset() tl := new(TabList) tl.List = make([]*Tab, len(bufs)) if tabBarVisible(len(bufs)) { for i, b := range bufs { tl.List[i] = NewTabFromBuffer(0, 1, w, h-1-iOffset, b) } } else { tl.List[0] = NewTabFromBuffer(0, 0, w, h-iOffset, bufs[0]) } tl.TabWindow = display.NewTabWindow(w, 0) tl.Names = make([]string, len(bufs))
return tl}
// UpdateNames makes sure that the list of names the tab window has access to is// correctfunc (t *TabList) UpdateNames() { t.Names = t.Names[:0] for _, p := range t.List { t.Names = append(t.Names, p.Panes[p.active].Name()) }}
// AddTab adds a new tab to this TabListfunc (t *TabList) AddTab(p *Tab) { t.List = append(t.List, p) t.Resize() t.UpdateNames()}
// RemoveTab removes a tab with the given id from the TabListfunc (t *TabList) RemoveTab(id uint64) { for i, p := range t.List { if len(p.Panes) == 0 { continue } if p.Panes[0].ID() == id { copy(t.List[i:], t.List[i+1:]) t.List[len(t.List)-1] = nil t.List = t.List[:len(t.List)-1] if t.Active() >= len(t.List) { t.SetActive(len(t.List) - 1) } t.Resize() t.UpdateNames() return } }}
// tabBarVisible reports whether the tab bar should be drawn. It is normally// only shown when more than one tab is open, but the tabalways option forces// it to always be visiblefunc tabBarVisible(numTabs int) bool { return numTabs > 1 || config.GetGlobalOption("tabalways").(bool)}
// Resize resizes all elements within the tab list// One thing to note is that when the tab bar is hidden (only 1 tab open and// tabalways off) the panes occupy the full height, so resizing must take// that into accountfunc (t *TabList) Resize() { w, h := screen.Screen.Size() iOffset := config.GetInfoBarOffset() InfoBar.Resize(w, h-1) if tabBarVisible(len(t.List)) { for _, p := range t.List { p.Y = 1 p.Node.Resize(w, h-1-iOffset) p.Resize() } } else if len(t.List) == 1 { t.List[0].Y = 0 t.List[0].Node.Resize(w, h-iOffset) t.List[0].Resize() } t.TabWindow.Resize(w, h)}
// HandleEvent checks for a resize event or a mouse event on the tab bar// otherwise it will forward the event to the currently active tabfunc (t *TabList) HandleEvent(event tcell.Event) { switch e := event.(type) { case *tcell.EventResize: t.Resize() case *tcell.EventMouse: mx, my := e.Position() switch e.Buttons() { case tcell.Button1: if my == t.Y && tabBarVisible(len(t.List)) { if mx == 0 { t.Scroll(-4) } else if mx == t.Width-1 { t.Scroll(4) } else { ind := t.LocFromVisual(buffer.Loc{mx, my}) if ind != -1 { t.SetActive(ind) } } return } case tcell.ButtonNone: if t.List[t.Active()].release { // Mouse release received, while already released t.ResetMouse() return } case tcell.WheelUp: if my == t.Y && tabBarVisible(len(t.List)) { t.Scroll(4) return } case tcell.WheelDown: if my == t.Y && tabBarVisible(len(t.List)) { t.Scroll(-4) return } } } t.List[t.Active()].HandleEvent(event)}
// Display updates the names and then displays the tab barfunc (t *TabList) Display() { t.UpdateNames() if tabBarVisible(len(t.List)) { t.TabWindow.Display() }}
func (t *TabList) SetActive(a int) { t.TabWindow.SetActive(a)
for i, p := range t.List { if i == a { if !p.isActive { p.isActive = true
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, p.CurPane())) if err != nil { screen.TermMessage(err) } } } else { p.isActive = false } }}
// ResetMouse resets the mouse release state after the screen was stopped// or the pane changed.// This prevents situations in which mouse releases are received at the wrong place// and the mouse state is still pressed.func (t *TabList) ResetMouse() { for _, tab := range t.List { if !tab.release && tab.resizing != nil { tab.resizing = nil }
tab.release = true
for _, p := range tab.Panes { if bp, ok := p.(*BufPane); ok { bp.resetMouse() } } }}
// CloseTerms notifies term panes that a terminal job has finished.func (t *TabList) CloseTerms() { for _, tab := range t.List { for _, p := range tab.Panes { if tp, ok := p.(*TermPane); ok { tp.HandleTermClose() } } }}
// Tabs is the global tab listvar Tabs *TabList
func InitTabs(bufs []*buffer.Buffer) { multiopen := config.GetGlobalOption("multiopen").(string) if multiopen == "tab" { Tabs = NewTabList(bufs) } else { Tabs = NewTabList(bufs[:1]) for _, b := range bufs[1:] { if multiopen == "vsplit" { MainTab().CurPane().VSplitBuf(b) } else { // default hsplit MainTab().CurPane().HSplitBuf(b) } } }
screen.RestartCallback = Tabs.ResetMouse}
func MainTab() *Tab { return Tabs.List[Tabs.Active()]}
// A Tab represents a single tab// It consists of a list of edit panes (the open buffers),// a split tree (stored as just the root node), and a uiwindow// to display the UI elements like the borders between splitstype Tab struct { *views.Node *display.UIWindow
isActive bool
Panes []Pane active int
resizing *views.Node // node currently being resized // captures whether the mouse is released release bool}
// NewTabFromBuffer creates a new tab from the given bufferfunc NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab { t := new(Tab) t.Node = views.NewRoot(x, y, width, height) t.UIWindow = display.NewUIWindow(t.Node) t.release = true
e := NewBufPaneFromBuf(b, t) e.SetID(t.ID())
t.Panes = append(t.Panes, e) return t}
func NewTabFromPane(x, y, width, height int, pane Pane) *Tab { t := new(Tab) t.Node = views.NewRoot(x, y, width, height) t.UIWindow = display.NewUIWindow(t.Node) t.release = true pane.SetTab(t) pane.SetID(t.ID())
t.Panes = append(t.Panes, pane) return t}
// HandleEvent takes a tcell event and usually dispatches it to the current// active pane. However if the event is a resize or a mouse event where the user// is interacting with the UI (resizing splits) then the event is consumed here// If the event is a mouse press event in a pane, that pane will become active// and get the eventfunc (t *Tab) HandleEvent(event tcell.Event) { switch e := event.(type) { case *tcell.EventMouse: mx, my := e.Position() btn := e.Buttons() switch { case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone: // button press or drag wasReleased := t.release t.release = false
if btn == tcell.Button1 { if t.resizing != nil { var size int if t.resizing.Kind == views.STVert { size = mx - t.resizing.X } else { size = my - t.resizing.Y + 1 } t.resizing.ResizeSplit(size) t.Resize() return } if wasReleased { t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my}) if t.resizing != nil { return } } }
if wasReleased { for i, p := range t.Panes { v := p.GetView() inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height if inpane { t.SetActive(i) break } } } case btn == tcell.ButtonNone: // button release t.release = true if t.resizing != nil { t.resizing = nil return } default: // wheel move for _, p := range t.Panes { v := p.GetView() inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height if inpane { p.HandleEvent(event) return } } }
} t.Panes[t.active].HandleEvent(event)}
// SetActive changes the currently active pane to the specified indexfunc (t *Tab) SetActive(i int) { t.active = i for j, p := range t.Panes { if j == i { p.SetActive(true) } else { p.SetActive(false) } }}
// AddPane adds a pane at a given indexfunc (t *Tab) AddPane(pane Pane, i int) { if len(t.Panes) == i { t.Panes = append(t.Panes, pane) return } t.Panes = append(t.Panes[:i+1], t.Panes[i:]...) t.Panes[i] = pane}
// GetPane returns the pane with the given split indexfunc (t *Tab) GetPane(splitid uint64) int { for i, p := range t.Panes { if p.ID() == splitid { return i } } return 0}
// Remove pane removes the pane with the given indexfunc (t *Tab) RemovePane(i int) { copy(t.Panes[i:], t.Panes[i+1:]) t.Panes[len(t.Panes)-1] = nil t.Panes = t.Panes[:len(t.Panes)-1]}
// Resize resizes all panes according to their corresponding split nodesfunc (t *Tab) Resize() { for _, p := range t.Panes { n := t.GetNode(p.ID()) pv := p.GetView() offset := 0 if n.X != 0 { offset = 1 } pv.X, pv.Y = n.X+offset, n.Y p.SetView(pv) p.Resize(n.W-offset, n.H) }}
// CurPane returns the currently active panefunc (t *Tab) CurPane() *BufPane { p, ok := t.Panes[t.active].(*BufPane) if !ok { return nil } return p}