From 1a478cdb53ec20fd7a44f7d9bcb2474589b5d80e Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Mon, 29 Jul 2019 21:04:26 -0400 Subject: [PATCH] terminal/command: add support for next [count] (#1629) * terminal/command: add support for next [count] * disallow negative counts. * handle github comments, and regen docs. * Fix the fact that we don't print the file info in the last step of the next count. * Fix a typo, cleanup a few other observations. --- Documentation/cli/README.md | 5 ++++ pkg/terminal/command.go | 54 ++++++++++++++++++++++++++++-------- pkg/terminal/command_test.go | 9 ++++++ 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index e90389ec..e51ce419 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -296,6 +296,11 @@ If regex is specified only local variables with a name matching it will be retur ## next Step over to next source line. + next [count] + +Optional [count] argument allows you to skip multiple lines. + + Aliases: n ## on diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 1823eef7..91d0fa29 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -138,7 +138,12 @@ the arguments. With -noargs, the process starts with an empty commandline. {aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."}, {aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."}, {aliases: []string{"step-instruction", "si"}, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."}, - {aliases: []string{"next", "n"}, cmdFn: c.next, helpMsg: "Step over to next source line."}, + {aliases: []string{"next", "n"}, cmdFn: c.next, helpMsg: `Step over to next source line. + + next [count] + +Optional [count] argument allows you to skip multiple lines. +`}, {aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."}, {aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!) @@ -864,6 +869,15 @@ func parseArgs(args string) ([]string, error) { return v[0], nil } +// parseOptionalCount parses an optional count argument. +// If there are not arguments, a value of 1 is returned as the default. +func parseOptionalCount(arg string) (int64, error) { + if len(arg) == 0 { + return 1, nil + } + return strconv.ParseInt(arg, 0, 64) +} + func restart(t *Term, ctx callContext, args string) error { v, err := parseArgs(args) if err != nil { @@ -932,9 +946,11 @@ func (c *Commands) cont(t *Term, ctx callContext, args string) error { return nil } -func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string) error { +func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, shouldPrintFile bool) error { if !state.NextInProgress { - printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) + if shouldPrintFile { + printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) + } return nil } for { @@ -983,7 +999,7 @@ func (c *Commands) step(t *Term, ctx callContext, args string) error { return err } printcontext(t, state) - return continueUntilCompleteNext(t, state, "step") + return continueUntilCompleteNext(t, state, "step", true) } var notOnFrameZeroErr = errors.New("not on topmost frame") @@ -1032,13 +1048,29 @@ func (c *Commands) next(t *Term, ctx callContext, args string) error { if c.frame != 0 { return notOnFrameZeroErr } - state, err := exitedToError(t.client.Next()) - if err != nil { - printcontextNoState(t) + var count int64 + var err error + if count, err = parseOptionalCount(args); err != nil { return err + } else if count <= 0 { + return errors.New("Invalid next count") } - printcontext(t, state) - return continueUntilCompleteNext(t, state, "next") + for ; count > 0; count-- { + state, err := exitedToError(t.client.Next()) + if err != nil { + printcontextNoState(t) + return err + } + // If we're about the exit the loop, print the context. + finishedNext := count == 1 + if finishedNext { + printcontext(t, state) + } + if err := continueUntilCompleteNext(t, state, "next", finishedNext); err != nil { + return err + } + } + return nil } func (c *Commands) stepout(t *Term, ctx callContext, args string) error { @@ -1054,7 +1086,7 @@ func (c *Commands) stepout(t *Term, ctx callContext, args string) error { return err } printcontext(t, state) - return continueUntilCompleteNext(t, state, "stepout") + return continueUntilCompleteNext(t, state, "stepout", true) } func (c *Commands) call(t *Term, ctx callContext, args string) error { @@ -1074,7 +1106,7 @@ func (c *Commands) call(t *Term, ctx callContext, args string) error { return err } printcontext(t, state) - return continueUntilCompleteNext(t, state, "call") + return continueUntilCompleteNext(t, state, "call", true) } func clear(t *Term, ctx callContext, args string) error { diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 1237d192..ed11ae80 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -664,6 +664,15 @@ func TestCheckpoints(t *testing.T) { }) } +func TestNextWithCount(t *testing.T) { + test.AllowRecording(t) + withTestTerminal("nextcond", t, func(term *FakeTerminal) { + term.MustExec("break main.main") + listIsAt(t, term, "continue", 8, -1, -1) + listIsAt(t, term, "next 2", 10, -1, -1) + }) +} + func TestRestart(t *testing.T) { withTestTerminal("restartargs", t, func(term *FakeTerminal) { term.MustExec("break main.printArgs")