diff --git a/pkg/cy/api/tree.go b/pkg/cy/api/tree.go index 19faaa86..5c480cab 100644 --- a/pkg/cy/api/tree.go +++ b/pkg/cy/api/tree.go @@ -141,6 +141,10 @@ func (t *TreeModule) Parent(id tree.NodeID) *tree.NodeID { return &parentId } +func (t *TreeModule) Kill(id tree.NodeID) error { + return t.Tree.RemoveNode(id) +} + func (t *TreeModule) Root() tree.NodeID { return t.Tree.Root().Id() } diff --git a/pkg/cy/client.go b/pkg/cy/client.go index e692a903..5b3b9f82 100644 --- a/pkg/cy/client.go +++ b/pkg/cy/client.go @@ -46,6 +46,9 @@ type Client struct { outerLayers *screen.Layers renderer *stream.Renderer + // history is an array of all of the panes this client has attached to + history []tree.NodeID + raw emu.Terminal info screen.RenderContext } @@ -300,8 +303,35 @@ func (c *Client) Attach(node tree.Node) error { c.muxClient.Attach(c.Ctx(), pane.Screen()) + go func() { + select { + case <-c.Ctx().Done(): + return + case <-c.muxClient.Attachment().Ctx().Done(): + return + case <-pane.Ctx().Done(): + // if the pane dies, just re-attach to the last node the user visited + c.RLock() + history := c.history + c.RUnlock() + + if len(history) < 2 { + // TODO(cfoust): 09/20/23 + return + } + + node, ok := c.cy.tree.NodeById(history[len(history)-2]) + if !ok { + return + } + c.Attach(node) + return + } + }() + c.Lock() c.node = node + c.history = append(c.history, node.Id()) c.Unlock() // Update bindings diff --git a/pkg/cy/cy-boot.janet b/pkg/cy/cy-boot.janet index 2f7fd42d..fae3ea1f 100644 --- a/pkg/cy/cy-boot.janet +++ b/pkg/cy/cy-boot.janet @@ -37,6 +37,12 @@ (0) # Gets the first index, the editor (pane/attach)))) +(key/bind + [prefix "x"] + "kill the current pane" + (fn [&] + (tree/kill (pane/current)))) + (key/bind [prefix "l"] "jump to a shell" diff --git a/pkg/cy/cy_test.go b/pkg/cy/cy_test.go index f35e4cce..2fa49ec6 100644 --- a/pkg/cy/cy_test.go +++ b/pkg/cy/cy_test.go @@ -186,3 +186,21 @@ func TestScopes(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, len(client.binds.Scopes())) } + +func TestPaneKill(t *testing.T) { + server := setupServer(t) + defer server.Release() + + _, client, err := server.Standard() + require.NoError(t, err) + + require.NoError(t, client.execute(` +(def shell (cmd/new (tree/root) "/tmp")) +(pp shell) +(pane/attach shell) +(tree/kill shell) +`)) + time.Sleep(100 * time.Millisecond) // lol + leaves := server.cy.tree.Leaves() + require.Equal(t, leaves[0].Id(), client.Node().Id()) +} diff --git a/pkg/cy/janet.go b/pkg/cy/janet.go index 024b3751..11fc3206 100644 --- a/pkg/cy/janet.go +++ b/pkg/cy/janet.go @@ -24,6 +24,14 @@ type CmdParams struct { Args []string } +// execute runs some Janet code on behalf of the Client. This is only used in testing. +func (c *Client) execute(code string) error { + return c.cy.janet.ExecuteCall(c.Ctx(), c, janet.Call{ + Code: []byte(code), + Options: janet.DEFAULT_CALL_OPTIONS, + }) +} + func (c *Cy) initJanet(ctx context.Context) (*janet.VM, error) { vm, err := janet.New(ctx) if err != nil { diff --git a/pkg/mux/screen/server/client.go b/pkg/mux/screen/server/client.go index 181a5195..d3bd0fd6 100644 --- a/pkg/mux/screen/server/client.go +++ b/pkg/mux/screen/server/client.go @@ -36,6 +36,10 @@ func (c *Client) State() *tty.State { return screen.State() } +func (c *Client) Attachment() *util.Lifetime { + return c.attachment +} + func (c *Client) Updates() *mux.Updater { return c.publisher.Subscribe() }