From 9e1623cff4d96e4f8ebb4b6dc6b9292d5406a747 Mon Sep 17 00:00:00 2001 From: Artem Lytkin Date: Sat, 5 Sep 2026 10:19:11 +0300 Subject: [PATCH] action: Keep the tab ID stable once the tab has splits `Tab` embeds its root `views.Node`, so `tab:ID()` from Lua resolved to `Node.ID()`, which returns 0 for any node that has children. As soon as a tab contained a split its root stopped being a leaf and the id was gone. Give `Tab` its own id, taken from the root node when the tab is created, so it stays the same after splitting. An unsplit tab reports the same value as before. --- internal/action/tab.go | 13 ++++++++++++ internal/action/tab_test.go | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 internal/action/tab_test.go diff --git a/internal/action/tab.go b/internal/action/tab.go index e1672a9332..76c984da73 100644 --- a/internal/action/tab.go +++ b/internal/action/tab.go @@ -239,6 +239,11 @@ type Tab struct { *views.Node *display.UIWindow + // id is this tab's unique id. It is taken from the root node when the + // tab is created and does not change afterwards, unlike the id of the + // embedded root node, which becomes 0 once the node has children. + id uint64 + isActive bool Panes []Pane @@ -253,6 +258,7 @@ type Tab struct { func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab { t := new(Tab) t.Node = views.NewRoot(x, y, width, height) + t.id = t.Node.ID() t.UIWindow = display.NewUIWindow(t.Node) t.release = true @@ -266,6 +272,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab { func NewTabFromPane(x, y, width, height int, pane Pane) *Tab { t := new(Tab) t.Node = views.NewRoot(x, y, width, height) + t.id = t.Node.ID() t.UIWindow = display.NewUIWindow(t.Node) t.release = true pane.SetTab(t) @@ -275,6 +282,12 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab { return t } +// ID returns this tab's unique id. Unlike the id of the tab's root node, it +// stays the same after the tab has been split. +func (t *Tab) ID() uint64 { + return t.id +} + // 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 diff --git a/internal/action/tab_test.go b/internal/action/tab_test.go new file mode 100644 index 0000000000..88e6b198c0 --- /dev/null +++ b/internal/action/tab_test.go @@ -0,0 +1,40 @@ +package action + +import ( + "testing" + + "github.com/micro-editor/micro/v2/internal/buffer" + "github.com/micro-editor/micro/v2/internal/config" + ulua "github.com/micro-editor/micro/v2/internal/lua" + lua "github.com/yuin/gopher-lua" +) + +func init() { + ulua.L = lua.NewState() + config.InitRuntimeFiles(false) + config.InitGlobalSettings() + config.GlobalSettings["backup"] = false + config.GlobalSettings["fastdirty"] = true +} + +func newTestTab() *Tab { + return NewTabFromBuffer(0, 0, 80, 24, buffer.NewBufferFromString("", "", buffer.BTDefault)) +} + +func TestTabIDWithSplits(t *testing.T) { + tab1 := newTestTab() + tab2 := newTestTab() + + id := tab1.ID() + if id == 0 { + t.Fatal("tab id is 0") + } + if tab2.ID() == id { + t.Fatalf("tabs share id %d", id) + } + + tab1.VSplit(true) + if got := tab1.ID(); got != id { + t.Errorf("tab id changed from %d to %d after split", id, got) + } +}