diff --git a/internal/config/config.go b/internal/config/config.go index aebcc07..94b12af 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "os" "path/filepath" "runtime" @@ -148,6 +149,25 @@ func defaultSeedDir(leaf string) string { return filepath.Join(home, ".config", "ctask", leaf) } +// ResolveSessionMode returns "direct" or "persistent" based on CTASK_SESSION_MODE. +// Default (unset/empty) is "direct". Any other value falls back to "direct" +// after printing a stderr warning. Used by entry commands (new, resume, last, +// open) to dispatch between the standard session.Run path and the tmux-backed +// persistent path. +func ResolveSessionMode() string { + v := os.Getenv("CTASK_SESSION_MODE") + switch v { + case "", "direct": + return "direct" + case "persistent": + return "persistent" + default: + fmt.Fprintf(os.Stderr, + "[ctask] warning: CTASK_SESSION_MODE=%q is not recognized; using direct mode\n", v) + return "direct" + } +} + // expandPath expands a leading ~/ and resolves to an absolute path when possible. func expandPath(p string) string { if strings.HasPrefix(p, "~/") || p == "~" { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1814897..c88e66f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,9 +1,11 @@ package config import ( + "io" "os" "path/filepath" "runtime" + "strings" "testing" ) @@ -191,3 +193,61 @@ func TestEnvVarsLaunchDirEmpty(t *testing.T) { t.Errorf("CTASK_LAUNCH_DIR: got %q, want empty", got) } } + +func TestResolveSessionModeDefault(t *testing.T) { + os.Unsetenv("CTASK_SESSION_MODE") + if got := ResolveSessionMode(); got != "direct" { + t.Errorf("default: got %q, want %q", got, "direct") + } +} + +func TestResolveSessionModeEmpty(t *testing.T) { + os.Setenv("CTASK_SESSION_MODE", "") + defer os.Unsetenv("CTASK_SESSION_MODE") + if got := ResolveSessionMode(); got != "direct" { + t.Errorf("empty: got %q, want %q", got, "direct") + } +} + +func TestResolveSessionModeDirect(t *testing.T) { + os.Setenv("CTASK_SESSION_MODE", "direct") + defer os.Unsetenv("CTASK_SESSION_MODE") + if got := ResolveSessionMode(); got != "direct" { + t.Errorf("direct: got %q, want %q", got, "direct") + } +} + +func TestResolveSessionModePersistent(t *testing.T) { + os.Setenv("CTASK_SESSION_MODE", "persistent") + defer os.Unsetenv("CTASK_SESSION_MODE") + if got := ResolveSessionMode(); got != "persistent" { + t.Errorf("persistent: got %q, want %q", got, "persistent") + } +} + +func TestResolveSessionModeUnknownFallsBackWithWarning(t *testing.T) { + os.Setenv("CTASK_SESSION_MODE", "garbage") + defer os.Unsetenv("CTASK_SESSION_MODE") + + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("pipe: %v", err) + } + origStderr := os.Stderr + os.Stderr = w + defer func() { os.Stderr = origStderr }() + + got := ResolveSessionMode() + w.Close() + out, _ := io.ReadAll(r) + + if got != "direct" { + t.Errorf("unknown: got %q, want %q (fallback)", got, "direct") + } + if !strings.Contains(string(out), "garbage") { + t.Errorf("warning should echo bad value: %q", out) + } + if !strings.Contains(string(out), "not recognized") { + t.Errorf("warning should say 'not recognized': %q", out) + } +}