diff --git a/pkg/cli/display/help.go b/pkg/cli/display/help.go index dd5ad7e9..c996d2a1 100644 --- a/pkg/cli/display/help.go +++ b/pkg/cli/display/help.go @@ -1,71 +1,12 @@ package display import ( - "strings" - "github.com/innerr/ticat/pkg/core/model" - "github.com/innerr/ticat/pkg/utils" ) func PrintGlobalHelp(cc *model.Cli, env *model.Env) { // TODO: use man page instead of help PrintSelfHelp(cc.Screen, env) - return - - if len(cc.Helps.Sections) == 0 { - PrintSelfHelp(cc.Screen, env) - return - } - - // TODO: with color output this is not right, disable it by setting to a very big value - _, width := utils.GetTerminalWidth(50, 100) - width = 4096 - - pln := func(line string) { - line = DecodeColor(line, env) - if len(line) <= width { - _ = cc.Screen.Print(line + "\n") - return - } - - indent := 0 - for _, char := range line { - if char != ' ' && char != '\t' { - break - } - indent += 1 - } - prefix := line[:indent] - - printWithPrefix := func(printed bool, line string) { - if printed { - _ = cc.Screen.Print(prefix) - _ = cc.Screen.Print(strings.TrimLeft(line, " \t") + "\n") - } else { - _ = cc.Screen.Print(line + "\n") - } - } - - printed := false - for { - if len(line) > width { - printWithPrefix(printed, line[:width]) - line = line[width:] - printed = true - continue - } else { - printWithPrefix(printed, line) - } - break - } - } - - for _, help := range cc.Helps.Sections { - PrintTipTitle(cc.Screen, env, help.Title) - for _, line := range help.Text { - pln(line) - } - } } func PrintSelfHelp(screen model.Screen, env *model.Env) { diff --git a/pkg/mods/builtin/builtin.go b/pkg/mods/builtin/builtin.go index e5516840..384b1389 100644 --- a/pkg/mods/builtin/builtin.go +++ b/pkg/mods/builtin/builtin.go @@ -985,59 +985,10 @@ func RegisterBlenderCmds(cmds *model.CmdTree) { AddArg("dest", "") // TODO: disable blenders now, too many bugs - return - - remove := cmds.AddSub("remove", "rm", "delete", "del"). - RegPowerCmd(BlenderRemoveOnce, - "during executing, remove a command in flow (only remove once)"). - SetQuiet(). - SetIsBlenderCmd(). - AddArg("target", "") - - remove.AddSub("all"). - RegPowerCmd(BlenderRemoveForAll, - "during executing, remove a command in flow (remove all)"). - SetQuiet(). - SetIsBlenderCmd(). - AddArg("target", "") - - insert := cmds.AddSub("insert", "ins"). - RegPowerCmd(BlenderInsertOnce, - "during executing, insert a command before another matched one (insert once)"). - SetQuiet(). - SetIsBlenderCmd(). - AddArg("target", ""). - AddArg("new", "") - - insert.AddSub("all"). - RegPowerCmd(BlenderInsertForAll, - "during executing, insert a command before another matched one (insert for all matched)"). - SetQuiet(). - SetIsBlenderCmd(). - AddArg("target", ""). - AddArg("new", "") - - after := insert.AddSub("after"). - RegPowerCmd(BlenderInsertAfterOnce, - "during executing, insert a command after another matched one (insert once)"). - SetQuiet(). - SetIsBlenderCmd(). - AddArg("target", ""). - AddArg("new", "") - - after.AddSub("all"). - RegPowerCmd(BlenderInsertAfterForAll, - "during executing, insert a command after another matched one (insert for all matched)"). - SetQuiet(). - SetIsBlenderCmd(). - AddArg("target", ""). - AddArg("new", "") - - cmds.AddSub("clear", "clean", "reset"). - RegPowerCmd(BlenderClear, - "clear all flow modification schedulings"). - SetQuiet(). - SetIsBlenderCmd() + // The following blender commands are temporarily disabled: + // - blender.remove, blender.remove.all + // - blender.insert, blender.insert.all, blender.insert.after, blender.insert.after.all + // - blender.clear } func RegisterCtrlCmds(cmds *model.CmdTree) { diff --git a/pkg/ticat/ticat_test.go b/pkg/ticat/ticat_test.go index eebb87d8..2d7550a1 100644 --- a/pkg/ticat/ticat_test.go +++ b/pkg/ticat/ticat_test.go @@ -2374,3 +2374,123 @@ func TestEnvDeepNestingIntegrity(t *testing.T) { } }) } + +func TestSubflowEnvIsolation(t *testing.T) { + t.Run("subflow creates separate env layer", func(t *testing.T) { + tc := NewTiCatForTest() + screen := &argsCaptureScreen{} + tc.SetScreen(screen) + tc.Env.SetBool("sys.panic.recover", false) + + tc.Env.Set("test.original", "original-value") + + ok := tc.RunCli("env.set", "key=test.new", "value=new-value", ":", "env.assert.equal", "key=test.original", "value=original-value") + if !ok { + t.Error("flow should succeed") + } + + if tc.Env.Get("test.new").Raw != "new-value" { + t.Error("new key should be set in session layer") + } + }) + + t.Run("env changes persist across flow commands", func(t *testing.T) { + tc := NewTiCatForTest() + screen := &argsCaptureScreen{} + tc.SetScreen(screen) + tc.Env.SetBool("sys.panic.recover", false) + + ok := tc.RunCli( + "env.set", "key=persist.a", "value=1", + ":", "noop", + ":", "env.update", "key=persist.a", "value=2", + ":", "noop", + ":", "env.assert.equal", "key=persist.a", "value=2") + if !ok { + t.Error("env changes should persist through flow") + } + }) +} + +func TestNestedSubflowEnv(t *testing.T) { + t.Run("deeply nested flow maintains env integrity", func(t *testing.T) { + tc := NewTiCatForTest() + screen := &argsCaptureScreen{} + tc.SetScreen(screen) + tc.Env.SetBool("sys.panic.recover", false) + + args := []string{"env.set", "key=nested.counter", "value=0", ":"} + for i := 1; i <= 5; i++ { + args = append(args, "env.update", "key=nested.counter", fmt.Sprintf("value=%d", i)) + if i < 5 { + args = append(args, ":") + } + } + + ok := tc.RunCli(args...) + if !ok { + t.Error("nested flow should succeed") + } + + if tc.Env.Get("nested.counter").Raw != "5" { + t.Errorf("counter should be 5, got %s", tc.Env.Get("nested.counter").Raw) + } + }) +} + +func TestForestModeEnvReset(t *testing.T) { + t.Run("forest mode isolates command env", func(t *testing.T) { + tc := NewTiCatForTest() + screen := &argsCaptureScreen{} + tc.SetScreen(screen) + tc.Env.SetBool("sys.panic.recover", false) + tc.Env.SetBool("sys.forest-mode", true) + + tc.Env.Set("forest.base", "base-value") + + ok := tc.RunCli( + "noop", + ":", "noop", + ":", "env.assert.equal", "key=forest.base", "value=base-value") + if !ok { + t.Error("forest mode flow should succeed") + } + }) +} + +func TestSubflowFailureEnvHandling(t *testing.T) { + t.Run("env integrity after multiple operations", func(t *testing.T) { + tc := NewTiCatForTest() + screen := &argsCaptureScreen{} + tc.SetScreen(screen) + tc.Env.SetBool("sys.panic.recover", false) + + ok := tc.RunCli( + "env.set", "key=integrity.a", "value=1", + ":", "env.set", "key=integrity.b", "value=2", + ":", "env.update", "key=integrity.a", "value=1-modified", + ":", "env.assert.equal", "key=integrity.a", "value=1-modified", + ":", "env.assert.equal", "key=integrity.b", "value=2") + if !ok { + t.Error("env integrity should be maintained") + } + }) + + t.Run("flow continues after successful operations", func(t *testing.T) { + tc := NewTiCatForTest() + screen := &argsCaptureScreen{} + tc.SetScreen(screen) + tc.Env.SetBool("sys.panic.recover", false) + + ok := tc.RunCli( + "env.set", "key=flow.step1", "value=a", + ":", "env.set", "key=flow.step2", "value=b", + ":", "env.set", "key=flow.step3", "value=c", + ":", "env.assert.equal", "key=flow.step1", "value=a", + ":", "env.assert.equal", "key=flow.step2", "value=b", + ":", "env.assert.equal", "key=flow.step3", "value=c") + if !ok { + t.Error("all flow steps should complete successfully") + } + }) +}