diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index ddcb4a63e7..ba2d3c514c 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -1342,6 +1342,7 @@ declare global { "term:bellindicator"?: boolean; "term:osc52"?: string; "term:durable"?: boolean; + "term:closeonlasttermclose"?: boolean; "editor:minimapenabled"?: boolean; "editor:stickyscrollenabled"?: boolean; "editor:wordwrap"?: boolean; diff --git a/pkg/blockcontroller/shellcontroller.go b/pkg/blockcontroller/shellcontroller.go index a410225394..5e23e3a893 100644 --- a/pkg/blockcontroller/shellcontroller.go +++ b/pkg/blockcontroller/shellcontroller.go @@ -647,6 +647,45 @@ func (union *ConnUnion) getRemoteInfoAndShellType(blockMeta waveobj.MetaMapType) return nil } +func isLastShellBlockInWorkspace(ctx context.Context, blockId string) bool { + tabId, err := wstore.DBFindTabForBlockId(ctx, blockId) + if err != nil || tabId == "" { + return false + } + workspaceId, err := wstore.DBFindWorkspaceForTabId(ctx, tabId) + if err != nil || workspaceId == "" { + return false + } + workspace, err := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId) + if err != nil || workspace == nil { + return false + } + shellBlockCount := 0 + for _, wsTabId := range workspace.TabIds { + tab, err := wstore.DBGet[*waveobj.Tab](ctx, wsTabId) + if err != nil { + return false + } + if tab == nil { + continue + } + for _, wsBlockId := range tab.BlockIds { + block, err := wstore.DBGet[*waveobj.Block](ctx, wsBlockId) + if err != nil { + return false + } + if block == nil { + continue + } + controller := block.Meta.GetString(waveobj.MetaKey_Controller, "") + if controller == BlockController_Shell || controller == BlockController_Cmd { + shellBlockCount++ + } + } + } + return shellBlockCount == 1 +} + func checkCloseOnExit(blockId string, exitCode int) { ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) defer cancelFn() @@ -657,10 +696,19 @@ func checkCloseOnExit(blockId string, exitCode int) { } closeOnExit := blockData.Meta.GetBool(waveobj.MetaKey_CmdCloseOnExit, false) closeOnExitForce := blockData.Meta.GetBool(waveobj.MetaKey_CmdCloseOnExitForce, false) + defaultDelayMs := 2000.0 if !closeOnExitForce && !(closeOnExit && exitCode == 0) { - return + // Check global setting: close when last terminal exits + settings := wconfig.GetWatcher().GetFullConfig().Settings + if !settings.TermCloseOnLastTermClose { + return + } + if !isLastShellBlockInWorkspace(ctx, blockId) { + return + } + defaultDelayMs = 0 } - delayMs := blockData.Meta.GetFloat(waveobj.MetaKey_CmdCloseOnExitDelay, 2000) + delayMs := blockData.Meta.GetFloat(waveobj.MetaKey_CmdCloseOnExitDelay, defaultDelayMs) if delayMs < 0 { delayMs = 0 } diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index 084dab1793..6eeca2cc6e 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -58,6 +58,7 @@ const ( ConfigKey_TermBellIndicator = "term:bellindicator" ConfigKey_TermOsc52 = "term:osc52" ConfigKey_TermDurable = "term:durable" + ConfigKey_TermCloseOnLastTermClose = "term:closeonlasttermclose" ConfigKey_EditorMinimapEnabled = "editor:minimapenabled" ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 17aafa6685..188a3205dc 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -109,6 +109,7 @@ type SettingsType struct { TermBellIndicator *bool `json:"term:bellindicator,omitempty"` TermOsc52 string `json:"term:osc52,omitempty" jsonschema:"enum=focus,enum=always"` TermDurable *bool `json:"term:durable,omitempty"` + TermCloseOnLastTermClose bool `json:"term:closeonlasttermclose,omitempty"` EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"` EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"` diff --git a/schema/settings.json b/schema/settings.json index 348c937dac..7a8e801213 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -161,6 +161,9 @@ "term:durable": { "type": "boolean" }, + "term:closeonlasttermclose": { + "type": "boolean" + }, "editor:minimapenabled": { "type": "boolean" },