micro/internal/action/tab.go

402 lines
9 KiB
Go
Raw Normal View History

2019-01-04 22:40:56 +00:00
package action
import (
luar "layeh.com/gopher-luar"
2020-05-04 14:16:15 +00:00
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
2020-05-04 14:16:15 +00:00
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views"
"github.com/micro-editor/tcell/v2"
2019-01-04 22:40:56 +00:00
)
2019-01-11 20:04:55 +00:00
// The TabList is a list of tabs and a window to display the tab bar
// at the top of the screen
2019-01-10 01:07:18 +00:00
type TabList struct {
*display.TabWindow
2019-01-11 19:49:22 +00:00
List []*Tab
2019-01-10 01:07:18 +00:00
}
2019-01-11 20:04:55 +00:00
// NewTabList creates a TabList from a list of buffers by creating a Tab
// for each buffer
2019-01-10 01:07:18 +00:00
func NewTabList(bufs []*buffer.Buffer) *TabList {
w, h := screen.Screen.Size()
2019-01-15 03:16:44 +00:00
iOffset := config.GetInfoBarOffset()
2019-01-10 01:07:18 +00:00
tl := new(TabList)
2019-01-11 19:49:22 +00:00
tl.List = make([]*Tab, len(bufs))
2019-01-10 01:07:18 +00:00
if len(bufs) > 1 {
for i, b := range bufs {
2019-01-15 03:16:44 +00:00
tl.List[i] = NewTabFromBuffer(0, 1, w, h-1-iOffset, b)
2019-01-10 01:07:18 +00:00
}
} else {
2019-01-15 03:16:44 +00:00
tl.List[0] = NewTabFromBuffer(0, 0, w, h-iOffset, bufs[0])
2019-01-10 01:07:18 +00:00
}
tl.TabWindow = display.NewTabWindow(w, 0)
tl.Names = make([]string, len(bufs))
return tl
}
2019-01-11 20:04:55 +00:00
// UpdateNames makes sure that the list of names the tab window has access to is
// correct
2019-01-10 01:07:18 +00:00
func (t *TabList) UpdateNames() {
t.Names = t.Names[:0]
for _, p := range t.List {
2019-01-11 19:49:22 +00:00
t.Names = append(t.Names, p.Panes[p.active].Name())
2019-01-10 01:07:18 +00:00
}
}
2019-01-04 22:40:56 +00:00
2019-01-11 20:04:55 +00:00
// AddTab adds a new tab to this TabList
2019-01-11 19:49:22 +00:00
func (t *TabList) AddTab(p *Tab) {
2019-01-10 04:44:53 +00:00
t.List = append(t.List, p)
t.Resize()
t.UpdateNames()
}
2019-01-11 20:04:55 +00:00
// RemoveTab removes a tab with the given id from the TabList
2019-01-10 04:44:53 +00:00
func (t *TabList) RemoveTab(id uint64) {
for i, p := range t.List {
if len(p.Panes) == 0 {
continue
}
2019-01-11 19:49:22 +00:00
if p.Panes[0].ID() == id {
2019-01-10 04:44:53 +00:00
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
}
}
}
2019-01-11 20:04:55 +00:00
// Resize resizes all elements within the tab list
// One thing to note is that when there is only 1 tab
// the tab bar should not be drawn so resizing must take
// that into account
2019-01-10 04:44:53 +00:00
func (t *TabList) Resize() {
w, h := screen.Screen.Size()
2019-01-15 03:16:44 +00:00
iOffset := config.GetInfoBarOffset()
2019-01-10 04:44:53 +00:00
InfoBar.Resize(w, h-1)
if len(t.List) > 1 {
for _, p := range t.List {
p.Y = 1
2019-01-15 03:16:44 +00:00
p.Node.Resize(w, h-1-iOffset)
2019-01-10 04:44:53 +00:00
p.Resize()
}
} else if len(t.List) == 1 {
t.List[0].Y = 0
2019-01-15 03:16:44 +00:00
t.List[0].Node.Resize(w, h-iOffset)
2019-01-10 04:44:53 +00:00
t.List[0].Resize()
}
2020-01-26 05:44:34 +00:00
t.TabWindow.Resize(w, h)
2019-01-10 04:44:53 +00:00
}
2019-01-11 20:04:55 +00:00
// HandleEvent checks for a resize event or a mouse event on the tab bar
// otherwise it will forward the event to the currently active tab
2019-01-10 01:07:18 +00:00
func (t *TabList) HandleEvent(event tcell.Event) {
switch e := event.(type) {
case *tcell.EventResize:
2019-01-10 04:44:53 +00:00
t.Resize()
2019-01-10 01:07:18 +00:00
case *tcell.EventMouse:
2019-01-10 03:13:50 +00:00
mx, my := e.Position()
2019-01-10 01:07:18 +00:00
switch e.Buttons() {
case tcell.Button1:
if my == t.Y && len(t.List) > 1 {
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
2019-01-10 03:13:50 +00:00
}
case tcell.ButtonNone:
if t.List[t.Active()].release {
// Mouse release received, while already released
t.ResetMouse()
return
}
2019-01-10 03:13:50 +00:00
case tcell.WheelUp:
if my == t.Y && len(t.List) > 1 {
2019-01-10 03:13:50 +00:00
t.Scroll(4)
return
}
case tcell.WheelDown:
if my == t.Y && len(t.List) > 1 {
2019-01-10 03:13:50 +00:00
t.Scroll(-4)
return
}
2019-01-10 01:07:18 +00:00
}
}
2019-01-10 04:44:53 +00:00
t.List[t.Active()].HandleEvent(event)
2019-01-10 01:07:18 +00:00
}
2019-01-11 20:04:55 +00:00
// Display updates the names and then displays the tab bar
2019-01-10 01:07:18 +00:00
func (t *TabList) Display() {
2019-01-10 04:44:53 +00:00
t.UpdateNames()
2019-01-10 01:07:18 +00:00
if len(t.List) > 1 {
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()
}
}
}
}
2019-01-11 20:04:55 +00:00
// Tabs is the global tab list
2019-01-10 01:07:18 +00:00
var 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
2019-01-10 01:07:18 +00:00
}
2019-01-11 19:49:22 +00:00
func MainTab() *Tab {
2019-01-10 04:44:53 +00:00
return Tabs.List[Tabs.Active()]
2019-01-10 01:07:18 +00:00
}
2019-01-11 19:49:22 +00:00
// A Tab represents a single tab
2019-01-10 01:07:18 +00:00
// 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 splits
2019-01-11 19:49:22 +00:00
type Tab struct {
2019-01-04 22:40:56 +00:00
*views.Node
2019-01-10 01:07:18 +00:00
*display.UIWindow
isActive bool
2019-01-11 19:49:22 +00:00
Panes []Pane
2019-01-04 22:40:56 +00:00
active int
2019-01-05 21:27:04 +00:00
2019-01-09 23:06:31 +00:00
resizing *views.Node // node currently being resized
// captures whether the mouse is released
release bool
2019-01-04 22:40:56 +00:00
}
2019-01-11 20:04:55 +00:00
// NewTabFromBuffer creates a new tab from the given buffer
2019-01-11 19:49:22 +00:00
func 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)
2020-08-04 22:41:14 +00:00
t.release = true
2019-01-11 19:49:22 +00:00
2020-02-05 22:16:31 +00:00
e := NewBufPaneFromBuf(b, t)
2019-01-19 20:37:59 +00:00
e.SetID(t.ID())
2019-01-11 19:49:22 +00:00
t.Panes = append(t.Panes, e)
return t
}
2019-01-19 20:37:59 +00:00
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)
2020-08-04 22:41:14 +00:00
t.release = true
2020-02-05 22:16:31 +00:00
pane.SetTab(t)
2019-01-19 20:37:59 +00:00
pane.SetID(t.ID())
t.Panes = append(t.Panes, pane)
return t
}
2019-01-10 01:07:18 +00:00
// 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 event
2019-01-11 19:49:22 +00:00
func (t *Tab) HandleEvent(event tcell.Event) {
2019-01-04 23:08:11 +00:00
switch e := event.(type) {
case *tcell.EventMouse:
2019-01-10 03:13:50 +00:00
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
2019-01-09 23:06:31 +00:00
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
}
}
}
2019-01-09 23:06:31 +00:00
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
}
2019-01-04 23:08:11 +00:00
}
}
case btn == tcell.ButtonNone:
// button release
t.release = true
if t.resizing != nil {
t.resizing = nil
return
}
2019-01-10 03:13:50 +00:00
default:
// wheel move
2019-01-10 03:13:50 +00:00
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
}
}
2019-01-04 23:08:11 +00:00
}
2019-01-09 23:06:31 +00:00
2019-01-04 23:08:11 +00:00
}
2019-01-04 22:40:56 +00:00
t.Panes[t.active].HandleEvent(event)
}
2019-01-10 01:07:18 +00:00
// SetActive changes the currently active pane to the specified index
2019-01-11 19:49:22 +00:00
func (t *Tab) SetActive(i int) {
2019-01-04 23:08:11 +00:00
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 index
func (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
}
2019-01-10 01:07:18 +00:00
// GetPane returns the pane with the given split index
2019-01-11 19:49:22 +00:00
func (t *Tab) GetPane(splitid uint64) int {
2019-01-09 21:55:00 +00:00
for i, p := range t.Panes {
2019-01-11 19:49:22 +00:00
if p.ID() == splitid {
2019-01-09 21:55:00 +00:00
return i
}
}
return 0
}
2019-01-10 01:07:18 +00:00
// Remove pane removes the pane with the given index
2019-01-11 19:49:22 +00:00
func (t *Tab) RemovePane(i int) {
2019-01-09 21:55:00 +00:00
copy(t.Panes[i:], t.Panes[i+1:])
2019-01-10 04:44:53 +00:00
t.Panes[len(t.Panes)-1] = nil
2019-01-09 21:55:00 +00:00
t.Panes = t.Panes[:len(t.Panes)-1]
}
2019-01-10 01:07:18 +00:00
// Resize resizes all panes according to their corresponding split nodes
2019-01-11 19:49:22 +00:00
func (t *Tab) Resize() {
2019-01-10 03:13:50 +00:00
for _, p := range t.Panes {
2019-01-11 19:49:22 +00:00
n := t.GetNode(p.ID())
2019-01-04 22:40:56 +00:00
pv := p.GetView()
2019-01-09 23:06:31 +00:00
offset := 0
2019-01-10 03:13:50 +00:00
if n.X != 0 {
2019-01-09 23:06:31 +00:00
offset = 1
}
pv.X, pv.Y = n.X+offset, n.Y
2019-01-04 22:40:56 +00:00
p.SetView(pv)
2019-01-09 23:06:31 +00:00
p.Resize(n.W-offset, n.H)
2019-01-04 22:40:56 +00:00
}
}
2019-01-10 01:07:18 +00:00
// CurPane returns the currently active pane
2020-02-02 22:12:50 +00:00
func (t *Tab) CurPane() *BufPane {
p, ok := t.Panes[t.active].(*BufPane)
if !ok {
return nil
}
return p
2019-01-04 22:40:56 +00:00
}