command.go 25.3 KB
Newer Older
1
// Package terminal implements functions for responding to user
D
Dan Mace 已提交
2 3 4 5 6 7
// input and dispatching to appropriate backend commands.
package terminal

import (
	"bufio"
	"fmt"
8 9
	"go/parser"
	"go/scanner"
10
	"io"
11
	"math"
D
Dan Mace 已提交
12 13 14 15 16
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
D
Derek Parker 已提交
17
	"text/tabwriter"
D
Dan Mace 已提交
18 19 20

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/api"
21
	"github.com/derekparker/delve/service/debugger"
D
Dan Mace 已提交
22 23
)

24 25
type cmdfunc func(t *Term, args string) error
type scopedCmdfunc func(t *Term, scope api.EvalScope, args string) error
26

27 28
type filteringFunc func(t *Term, filter string) ([]string, error)
type scopedFilteringFunc func(t *Term, scope api.EvalScope, filter string) ([]string, error)
D
Dan Mace 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

type command struct {
	aliases []string
	helpMsg string
	cmdFn   cmdfunc
}

// Returns true if the command string matches one of the aliases for this command
func (c command) match(cmdstr string) bool {
	for _, v := range c.aliases {
		if v == cmdstr {
			return true
		}
	}
	return false
}

D
Derek Parker 已提交
46
// Commands represents the commands for Delve terminal process.
D
Dan Mace 已提交
47
type Commands struct {
D
Derek Parker 已提交
48 49 50
	cmds    []command
	lastCmd cmdfunc
	client  service.Client
D
Dan Mace 已提交
51 52
}

D
Derek Parker 已提交
53
// DebugCommands returns a Commands struct with default commands defined.
D
Dan Mace 已提交
54 55 56 57 58
func DebugCommands(client service.Client) *Commands {
	c := &Commands{client: client}

	c.cmds = []command{
		{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
59
		{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <linespec> [-stack <n>|-goroutine|<variable name>]*"},
D
Derek Parker 已提交
60
		{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
D
Derek Parker 已提交
61
		{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
D
Dan Mace 已提交
62 63 64 65
		{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
		{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
		{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
		{aliases: []string{"threads"}, cmdFn: threads, helpMsg: "Print out info for every traced thread."},
D
Derek Parker 已提交
66
		{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
D
Dan Mace 已提交
67
		{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
68
		{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "clearall [<linespec>]. Deletes all breakpoints. If <linespec> is provided, only matching breakpoints will be deleted."},
69
		{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)] Print out info for every goroutine."},
70
		{aliases: []string{"goroutine"}, cmdFn: goroutine, helpMsg: "Sets current goroutine."},
D
Dan Mace 已提交
71
		{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
72
		{aliases: []string{"print", "p"}, cmdFn: g0f0(printVar), helpMsg: "Evaluate a variable."},
73
		{aliases: []string{"set"}, cmdFn: g0f0(setVar), helpMsg: "Changes the value of a variable."},
D
Derek Parker 已提交
74 75
		{aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."},
		{aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."},
76 77
		{aliases: []string{"args"}, cmdFn: filterSortAndOutput(g0f0filter(args)), helpMsg: "Print function arguments, optionally filtered by a regexp."},
		{aliases: []string{"locals"}, cmdFn: filterSortAndOutput(g0f0filter(locals)), helpMsg: "Print function locals, optionally filtered by a regexp."},
D
Derek Parker 已提交
78 79
		{aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."},
		{aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."},
D
Derek Parker 已提交
80
		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
81
		{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>.  Show source around current point or provided linespec."},
82
		{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth>] [-full]. Prints stack."},
83
		{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
84
		{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
D
Dan Mace 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
	}

	return c
}

// Register custom commands. Expects cf to be a func of type cmdfunc,
// returning only an error.
func (c *Commands) Register(cmdstr string, cf cmdfunc, helpMsg string) {
	for _, v := range c.cmds {
		if v.match(cmdstr) {
			v.cmdFn = cf
			return
		}
	}

	c.cmds = append(c.cmds, command{aliases: []string{cmdstr}, cmdFn: cf, helpMsg: helpMsg})
}

// Find will look up the command function for the given command input.
D
Derek Parker 已提交
104
// If it cannot find the command it will default to noCmdAvailable().
D
Dan Mace 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
// If the command is an empty string it will replay the last command.
func (c *Commands) Find(cmdstr string) cmdfunc {
	// If <enter> use last command, if there was one.
	if cmdstr == "" {
		if c.lastCmd != nil {
			return c.lastCmd
		}
		return nullCommand
	}

	for _, v := range c.cmds {
		if v.match(cmdstr) {
			c.lastCmd = v.cmdFn
			return v.cmdFn
		}
	}

	return noCmdAvailable
}

125 126 127 128 129 130 131 132 133
// Merge takes aliases defined in the config struct and merges them with the default aliases.
func (c *Commands) Merge(allAliases map[string][]string) {
	for i := range c.cmds {
		if aliases, ok := allAliases[c.cmds[i].aliases[0]]; ok {
			c.cmds[i].aliases = append(c.cmds[i].aliases, aliases...)
		}
	}
}

134
func noCmdAvailable(t *Term, args string) error {
D
Derek Parker 已提交
135
	return fmt.Errorf("command not available")
D
Dan Mace 已提交
136 137
}

138
func nullCommand(t *Term, args string) error {
D
Dan Mace 已提交
139 140 141
	return nil
}

142
func (c *Commands) help(t *Term, args string) error {
D
Dan Mace 已提交
143
	fmt.Println("The following commands are available:")
D
Derek Parker 已提交
144 145
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '-', 0)
D
Dan Mace 已提交
146
	for _, cmd := range c.cmds {
D
Derek Parker 已提交
147 148 149 150 151
		if len(cmd.aliases) > 1 {
			fmt.Fprintf(w, "    %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), cmd.helpMsg)
		} else {
			fmt.Fprintf(w, "    %s \t %s\n", cmd.aliases[0], cmd.helpMsg)
		}
D
Dan Mace 已提交
152
	}
D
Derek Parker 已提交
153
	return w.Flush()
D
Dan Mace 已提交
154 155
}

I
Ilia Choly 已提交
156
type byThreadID []*api.Thread
I
Ilia Choly 已提交
157

I
Ilia Choly 已提交
158 159 160
func (a byThreadID) Len() int           { return len(a) }
func (a byThreadID) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byThreadID) Less(i, j int) bool { return a[i].ID < a[j].ID }
I
Ilia Choly 已提交
161

162
func threads(t *Term, args string) error {
163
	threads, err := t.client.ListThreads()
D
Dan Mace 已提交
164 165 166
	if err != nil {
		return err
	}
167
	state, err := t.client.GetState()
D
Dan Mace 已提交
168 169 170
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
171
	sort.Sort(byThreadID(threads))
D
Dan Mace 已提交
172 173 174 175 176 177 178
	for _, th := range threads {
		prefix := "  "
		if state.CurrentThread != nil && state.CurrentThread.ID == th.ID {
			prefix = "* "
		}
		if th.Function != nil {
			fmt.Printf("%sThread %d at %#v %s:%d %s\n",
179
				prefix, th.ID, th.PC, ShortenFilePath(th.File),
D
Dan Mace 已提交
180 181
				th.Line, th.Function.Name)
		} else {
182
			fmt.Printf("%sThread %s\n", prefix, formatThread(th))
D
Dan Mace 已提交
183 184 185 186 187
		}
	}
	return nil
}

188
func thread(t *Term, args string) error {
189 190 191
	if len(args) == 0 {
		return fmt.Errorf("you must specify a thread")
	}
192
	tid, err := strconv.Atoi(args)
D
Dan Mace 已提交
193 194 195
	if err != nil {
		return err
	}
196
	oldState, err := t.client.GetState()
D
Dan Mace 已提交
197 198 199
	if err != nil {
		return err
	}
200
	newState, err := t.client.SwitchThread(tid)
D
Dan Mace 已提交
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
	if err != nil {
		return err
	}

	oldThread := "<none>"
	newThread := "<none>"
	if oldState.CurrentThread != nil {
		oldThread = strconv.Itoa(oldState.CurrentThread.ID)
	}
	if newState.CurrentThread != nil {
		newThread = strconv.Itoa(newState.CurrentThread.ID)
	}
	fmt.Printf("Switched from %s to %s\n", oldThread, newThread)
	return nil
}

I
Ilia Choly 已提交
217 218 219 220 221 222
type byGoroutineID []*api.Goroutine

func (a byGoroutineID) Len() int           { return len(a) }
func (a byGoroutineID) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }

223 224
func goroutines(t *Term, argstr string) error {
	args := strings.Split(argstr, " ")
225 226 227 228 229 230 231 232 233 234 235 236 237 238
	var fgl = fglUserCurrent

	switch len(args) {
	case 0:
		// nothing to do
	case 1:
		switch args[0] {
		case "-u":
			fgl = fglUserCurrent
		case "-r":
			fgl = fglRuntimeCurrent
		case "-g":
			fgl = fglGo
		default:
D
Derek Parker 已提交
239
			return fmt.Errorf("wrong argument: '%s'", args[0])
240 241 242 243
		}
	default:
		return fmt.Errorf("too many arguments")
	}
244
	state, err := t.client.GetState()
245 246 247
	if err != nil {
		return err
	}
248
	gs, err := t.client.ListGoroutines()
D
Dan Mace 已提交
249 250 251
	if err != nil {
		return err
	}
I
Ilia Choly 已提交
252
	sort.Sort(byGoroutineID(gs))
D
Dan Mace 已提交
253 254
	fmt.Printf("[%d goroutines]\n", len(gs))
	for _, g := range gs {
255
		prefix := "  "
256
		if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
257 258
			prefix = "* "
		}
259
		fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
260 261 262 263
	}
	return nil
}

264 265
func goroutine(t *Term, argstr string) error {
	if argstr == "" {
266
		return printscope(t)
267
	}
268

269 270
	if strings.Index(argstr, " ") < 0 {
		gid, err := strconv.Atoi(argstr)
271 272 273 274
		if err != nil {
			return err
		}

275
		oldState, err := t.client.GetState()
276 277 278
		if err != nil {
			return err
		}
279
		newState, err := t.client.SwitchGoroutine(gid)
280 281 282 283 284 285 286 287
		if err != nil {
			return err
		}

		fmt.Printf("Switched from %d to %d (thread %d)\n", oldState.SelectedGoroutine.ID, gid, newState.CurrentThread.ID)
		return nil
	}

288
	return scopePrefix(t, "goroutine "+argstr)
289 290
}

291 292 293
func frame(t *Term, args string) error {
	return scopePrefix(t, "frame "+args)
}
294

295
func scopePrefix(t *Term, cmdstr string) error {
D
Derek Parker 已提交
296
	scope := api.EvalScope{GoroutineID: -1, Frame: 0}
297
	lastcmd := ""
298 299 300 301 302 303 304 305 306 307 308
	rest := cmdstr

	nexttok := func() string {
		v := strings.SplitN(rest, " ", 2)
		if len(v) > 1 {
			rest = v[1]
		} else {
			rest = ""
		}
		return v[0]
	}
309

310
	callFilterSortAndOutput := func(fn scopedFilteringFunc, fnargs string) error {
311 312
		outfn := filterSortAndOutput(func(t *Term, filter string) ([]string, error) {
			return fn(t, scope, filter)
313
		})
314
		return outfn(t, fnargs)
315 316
	}

317 318 319 320 321 322
	for {
		cmd := nexttok()
		if cmd == "" && rest == "" {
			break
		}
		switch cmd {
323
		case "goroutine":
324
			if rest == "" {
325 326
				return fmt.Errorf("goroutine command needs an argument")
			}
327
			n, err := strconv.Atoi(nexttok())
328 329 330 331 332
			if err != nil {
				return fmt.Errorf("invalid argument to goroutine, expected integer")
			}
			scope.GoroutineID = int(n)
		case "frame":
333
			if rest == "" {
334 335
				return fmt.Errorf("frame command needs an argument")
			}
336
			n, err := strconv.Atoi(nexttok())
337 338 339 340
			if err != nil {
				return fmt.Errorf("invalid argument to frame, expected integer")
			}
			scope.Frame = int(n)
341 342
		case "list", "ls":
			frame, gid := scope.Frame, scope.GoroutineID
343
			locs, err := t.client.Stacktrace(gid, frame, false)
344 345 346 347 348 349 350
			if err != nil {
				return err
			}
			if frame >= len(locs) {
				return fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
			}
			loc := locs[frame]
351
			return printfile(t, loc.File, loc.Line, true)
352
		case "stack", "bt":
353
			depth, full, err := parseStackArgs(rest)
354 355 356
			if err != nil {
				return err
			}
357
			stack, err := t.client.Stacktrace(scope.GoroutineID, depth, full)
358 359 360 361 362
			if err != nil {
				return err
			}
			printStack(stack, "")
			return nil
363
		case "locals":
364
			return callFilterSortAndOutput(locals, rest)
365
		case "args":
366
			return callFilterSortAndOutput(args, rest)
367
		case "print", "p":
368 369 370
			return printVar(t, scope, rest)
		case "set":
			return setVar(t, scope, rest)
371
		default:
372
			return fmt.Errorf("unknown command %s", cmd)
373
		}
374
		lastcmd = cmd
375 376 377 378 379
	}

	return fmt.Errorf("no command passed to %s", lastcmd)
}

380 381
func printscope(t *Term) error {
	state, err := t.client.GetState()
382 383
	if err != nil {
		return err
D
Dan Mace 已提交
384
	}
385

386 387 388 389
	fmt.Printf("Thread %s\n", formatThread(state.CurrentThread))
	if state.SelectedGoroutine != nil {
		writeGoroutineLong(os.Stdout, state.SelectedGoroutine, "")
	}
D
Dan Mace 已提交
390 391 392
	return nil
}

393 394 395 396
func formatThread(th *api.Thread) string {
	if th == nil {
		return "<nil>"
	}
397
	return fmt.Sprintf("%d at %s:%d", th.ID, ShortenFilePath(th.File), th.Line)
398 399
}

400 401 402 403 404 405 406 407 408 409 410 411 412
type formatGoroutineLoc int

const (
	fglRuntimeCurrent = formatGoroutineLoc(iota)
	fglUserCurrent
	fglGo
)

func formatLocation(loc api.Location) string {
	fname := ""
	if loc.Function != nil {
		fname = loc.Function.Name
	}
413
	return fmt.Sprintf("%s:%d %s (%#v)", ShortenFilePath(loc.File), loc.Line, fname, loc.PC)
414 415 416
}

func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string {
417 418 419
	if g == nil {
		return "<nil>"
	}
420 421 422 423 424
	var locname string
	var loc api.Location
	switch fgl {
	case fglRuntimeCurrent:
		locname = "Runtime"
425
		loc = g.CurrentLoc
426 427
	case fglUserCurrent:
		locname = "User"
428
		loc = g.UserCurrentLoc
429 430
	case fglGo:
		locname = "Go"
431
		loc = g.GoStatementLoc
A
aarzilli 已提交
432
	}
433 434 435 436 437 438
	return fmt.Sprintf("%d - %s: %s", g.ID, locname, formatLocation(loc))
}

func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
	fmt.Fprintf(w, "%sGoroutine %d:\n%s\tRuntime: %s\n%s\tUser: %s\n%s\tGo: %s\n",
		prefix, g.ID,
439 440 441
		prefix, formatLocation(g.CurrentLoc),
		prefix, formatLocation(g.UserCurrentLoc),
		prefix, formatLocation(g.GoStatementLoc))
A
aarzilli 已提交
442 443
}

444
func restart(t *Term, args string) error {
445
	if err := t.client.Restart(); err != nil {
D
Derek Parker 已提交
446 447
		return err
	}
448
	fmt.Println("Process restarted with PID", t.client.ProcessPid())
D
Derek Parker 已提交
449 450 451
	return nil
}

452
func cont(t *Term, args string) error {
453
	stateChan := t.client.Continue()
D
Derek Parker 已提交
454
	for state := range stateChan {
A
aarzilli 已提交
455 456 457
		if state.Err != nil {
			return state.Err
		}
458
		printcontext(t, state)
D
Dan Mace 已提交
459 460 461 462
	}
	return nil
}

463
func step(t *Term, args string) error {
464
	state, err := t.client.Step()
D
Dan Mace 已提交
465 466 467
	if err != nil {
		return err
	}
468
	printcontext(t, state)
D
Dan Mace 已提交
469 470 471
	return nil
}

472
func next(t *Term, args string) error {
473
	state, err := t.client.Next()
D
Dan Mace 已提交
474 475 476
	if err != nil {
		return err
	}
477
	printcontext(t, state)
D
Dan Mace 已提交
478 479 480
	return nil
}

481
func clear(t *Term, args string) error {
D
Dan Mace 已提交
482
	if len(args) == 0 {
D
Derek Parker 已提交
483
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
484
	}
485
	id, err := strconv.Atoi(args)
D
Dan Mace 已提交
486 487 488
	if err != nil {
		return err
	}
489
	bp, err := t.client.ClearBreakpoint(id)
D
Dan Mace 已提交
490 491 492
	if err != nil {
		return err
	}
493
	fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
D
Dan Mace 已提交
494 495 496
	return nil
}

497
func clearAll(t *Term, args string) error {
498
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
499 500 501
	if err != nil {
		return err
	}
502 503

	var locPCs map[uint64]struct{}
504
	if args != "" {
D
Derek Parker 已提交
505
		locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
506 507 508 509 510 511 512 513 514
		if err != nil {
			return err
		}
		locPCs = make(map[uint64]struct{})
		for _, loc := range locs {
			locPCs[loc.PC] = struct{}{}
		}
	}

D
Dan Mace 已提交
515
	for _, bp := range breakPoints {
516 517 518 519 520 521
		if locPCs != nil {
			if _, ok := locPCs[bp.Addr]; !ok {
				continue
			}
		}

522
		_, err := t.client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
523
		if err != nil {
524
			fmt.Printf("Couldn't delete breakpoint %d at %#v %s:%d: %s\n", bp.ID, bp.Addr, ShortenFilePath(bp.File), bp.Line, err)
D
Dan Mace 已提交
525
		}
526
		fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
D
Dan Mace 已提交
527 528 529 530
	}
	return nil
}

D
Derek Parker 已提交
531 532
// ByID sorts breakpoints by ID.
type ByID []*api.Breakpoint
D
Dan Mace 已提交
533

D
Derek Parker 已提交
534 535 536
func (a ByID) Len() int           { return len(a) }
func (a ByID) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByID) Less(i, j int) bool { return a[i].ID < a[j].ID }
D
Dan Mace 已提交
537

538
func breakpoints(t *Term, args string) error {
539
	breakPoints, err := t.client.ListBreakpoints()
D
Dan Mace 已提交
540 541 542
	if err != nil {
		return err
	}
D
Derek Parker 已提交
543
	sort.Sort(ByID(breakPoints))
D
Dan Mace 已提交
544
	for _, bp := range breakPoints {
A
aarzilli 已提交
545 546 547 548
		thing := "Breakpoint"
		if bp.Tracepoint {
			thing = "Tracepoint"
		}
549
		fmt.Printf("%s %d at %#v %s:%d (%d)\n", thing, bp.ID, bp.Addr, ShortenFilePath(bp.File), bp.Line, bp.TotalHitCount)
A
aarzilli 已提交
550 551 552 553 554 555 556 557 558

		var attrs []string
		if bp.Stacktrace > 0 {
			attrs = append(attrs, "-stack")
			attrs = append(attrs, strconv.Itoa(bp.Stacktrace))
		}
		if bp.Goroutine {
			attrs = append(attrs, "-goroutine")
		}
D
Derek Parker 已提交
559 560
		for i := range bp.Variables {
			attrs = append(attrs, bp.Variables[i])
A
aarzilli 已提交
561 562 563 564
		}
		if len(attrs) > 0 {
			fmt.Printf("\t%s\n", strings.Join(attrs, " "))
		}
D
Dan Mace 已提交
565 566 567 568
	}
	return nil
}

569 570
func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
	args := strings.Split(argstr, " ")
A
aarzilli 已提交
571 572
	if len(args) < 1 {
		return fmt.Errorf("address required, specify either a function name or <file:line>")
D
Dan Mace 已提交
573
	}
D
Derek Parker 已提交
574
	requestedBp := &api.Breakpoint{}
D
Dan Mace 已提交
575

A
aarzilli 已提交
576 577 578 579 580 581 582 583 584 585 586 587
	for i := 1; i < len(args); i++ {
		switch args[i] {
		case "-stack":
			i++
			n, err := strconv.Atoi(args[i])
			if err != nil {
				return fmt.Errorf("argument of -stack must be a number")
			}
			requestedBp.Stacktrace = n
		case "-goroutine":
			requestedBp.Goroutine = true
		default:
D
Derek Parker 已提交
588
			requestedBp.Variables = append(requestedBp.Variables, args[i])
A
aarzilli 已提交
589 590 591 592
		}
	}

	requestedBp.Tracepoint = tracepoint
D
Derek Parker 已提交
593
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args[0])
D
Dan Mace 已提交
594 595 596
	if err != nil {
		return err
	}
A
aarzilli 已提交
597 598 599 600
	thing := "Breakpoint"
	if tracepoint {
		thing = "Tracepoint"
	}
601 602 603
	for _, loc := range locs {
		requestedBp.Addr = loc.PC

604
		bp, err := t.client.CreateBreakpoint(requestedBp)
605 606 607 608
		if err != nil {
			return err
		}

609
		fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
610
	}
D
Dan Mace 已提交
611 612 613
	return nil
}

614 615
func breakpoint(t *Term, args string) error {
	return setBreakpoint(t, false, args)
A
aarzilli 已提交
616 617
}

618 619
func tracepoint(t *Term, args string) error {
	return setBreakpoint(t, true, args)
A
aarzilli 已提交
620 621
}

622
func g0f0(fn scopedCmdfunc) cmdfunc {
623
	return func(t *Term, args string) error {
D
Derek Parker 已提交
624
		return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, args)
625 626 627 628
	}
}

func g0f0filter(fn scopedFilteringFunc) filteringFunc {
629
	return func(t *Term, filter string) ([]string, error) {
D
Derek Parker 已提交
630
		return fn(t, api.EvalScope{GoroutineID: -1, Frame: 0}, filter)
631 632 633
	}
}

634
func printVar(t *Term, scope api.EvalScope, args string) error {
D
Dan Mace 已提交
635
	if len(args) == 0 {
D
Derek Parker 已提交
636
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
637
	}
638
	val, err := t.client.EvalVariable(scope, args)
D
Dan Mace 已提交
639 640 641
	if err != nil {
		return err
	}
642 643

	fmt.Println(val.MultilineString(""))
D
Dan Mace 已提交
644 645 646
	return nil
}

647 648 649 650 651
func setVar(t *Term, scope api.EvalScope, args string) error {
	// HACK: in go '=' is not an operator, we detect the error and try to recover from it by splitting the input string
	_, err := parser.ParseExpr(args)
	if err == nil {
		return fmt.Errorf("syntax error '=' not found")
652 653
	}

654 655 656 657 658 659 660 661
	el, ok := err.(scanner.ErrorList)
	if !ok || el[0].Msg != "expected '==', found '='" {
		return err
	}

	lexpr := args[:el[0].Pos.Offset]
	rexpr := args[el[0].Pos.Offset+1:]
	return t.client.SetVariable(scope, lexpr, rexpr)
662 663
}

D
Derek Parker 已提交
664 665 666 667 668 669
func filterVariables(vars []api.Variable, filter string) []string {
	reg, err := regexp.Compile(filter)
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		return nil
	}
D
Dan Mace 已提交
670 671
	data := make([]string, 0, len(vars))
	for _, v := range vars {
D
Derek Parker 已提交
672
		if reg == nil || reg.Match([]byte(v.Name)) {
673
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
D
Dan Mace 已提交
674 675 676 677 678
		}
	}
	return data
}

679 680
func sources(t *Term, filter string) ([]string, error) {
	return t.client.ListSources(filter)
D
Derek Parker 已提交
681
}
D
Dan Mace 已提交
682

683 684
func funcs(t *Term, filter string) ([]string, error) {
	return t.client.ListFunctions(filter)
D
Derek Parker 已提交
685
}
D
Dan Mace 已提交
686

687 688
func args(t *Term, scope api.EvalScope, filter string) ([]string, error) {
	vars, err := t.client.ListFunctionArgs(scope)
D
Derek Parker 已提交
689 690 691 692 693
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
694

695 696
func locals(t *Term, scope api.EvalScope, filter string) ([]string, error) {
	locals, err := t.client.ListLocalVariables(scope)
D
Derek Parker 已提交
697 698 699 700 701
	if err != nil {
		return nil, err
	}
	return filterVariables(locals, filter), nil
}
D
Dan Mace 已提交
702

703 704
func vars(t *Term, filter string) ([]string, error) {
	vars, err := t.client.ListPackageVariables(filter)
D
Derek Parker 已提交
705 706 707 708 709
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
710

711
func regs(t *Term, args string) error {
712
	regs, err := t.client.ListRegisters()
D
Derek Parker 已提交
713 714 715 716 717 718
	if err != nil {
		return err
	}
	fmt.Println(regs)
	return nil
}
719

720
func filterSortAndOutput(fn filteringFunc) cmdfunc {
721
	return func(t *Term, args string) error {
D
Derek Parker 已提交
722
		var filter string
723 724
		if len(args) > 0 {
			if _, err := regexp.Compile(args); err != nil {
D
Derek Parker 已提交
725 726
				return fmt.Errorf("invalid filter argument: %s", err.Error())
			}
727
			filter = args
D
Dan Mace 已提交
728
		}
729
		data, err := fn(t, filter)
D
Dan Mace 已提交
730 731 732
		if err != nil {
			return err
		}
D
Derek Parker 已提交
733 734 735
		sort.Sort(sort.StringSlice(data))
		for _, d := range data {
			fmt.Println(d)
D
Dan Mace 已提交
736
		}
D
Derek Parker 已提交
737
		return nil
D
Dan Mace 已提交
738 739 740
	}
}

741
func stackCommand(t *Term, args string) error {
742 743 744 745 746 747 748 749
	var (
		err         error
		goroutineid = -1
	)
	depth, full, err := parseStackArgs(args)
	if err != nil {
		return err
	}
750
	stack, err := t.client.Stacktrace(goroutineid, depth, full)
751 752 753 754 755 756
	if err != nil {
		return err
	}
	printStack(stack, "")
	return nil
}
A
aarzilli 已提交
757

758
func parseStackArgs(argstr string) (int, bool, error) {
759 760 761 762
	var (
		depth = 10
		full  = false
	)
763 764 765 766 767 768 769 770 771 772 773
	if argstr != "" {
		args := strings.Split(argstr, " ")
		for i := range args {
			if args[i] == "-full" {
				full = true
			} else {
				n, err := strconv.Atoi(args[i])
				if err != nil {
					return 0, false, fmt.Errorf("depth must be a number")
				}
				depth = n
774
			}
A
aarzilli 已提交
775 776
		}
	}
777
	return depth, full, nil
A
aarzilli 已提交
778 779
}

780
func listCommand(t *Term, args string) error {
781
	if len(args) == 0 {
782
		state, err := t.client.GetState()
783 784 785
		if err != nil {
			return err
		}
786
		printcontext(t, state)
787 788 789
		return nil
	}

D
Derek Parker 已提交
790
	locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
791 792 793 794
	if err != nil {
		return err
	}
	if len(locs) > 1 {
795
		return debugger.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
796
	}
797
	printfile(t, locs[0].File, locs[0].Line, false)
798 799 800
	return nil
}

D
Derek Parker 已提交
801
func (c *Commands) sourceCommand(t *Term, args string) error {
802
	if len(args) == 0 {
803 804 805
		return fmt.Errorf("wrong number of arguments: source <filename>")
	}

D
Derek Parker 已提交
806
	return c.executeFile(t, args)
807 808
}

809 810 811 812 813 814 815
func digits(n int) int {
	return int(math.Floor(math.Log10(float64(n)))) + 1
}

func printStack(stack []api.Stackframe, ind string) {
	d := digits(len(stack) - 1)
	fmtstr := "%s%" + strconv.Itoa(d) + "d  0x%016x in %s\n"
816
	s := strings.Repeat(" ", d+2+len(ind))
817

A
aarzilli 已提交
818 819 820 821 822
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
823
		fmt.Printf(fmtstr, ind, i, stack[i].PC, name)
824
		fmt.Printf("%sat %s:%d\n", s, ShortenFilePath(stack[i].File), stack[i].Line)
825 826

		for j := range stack[i].Arguments {
827
			fmt.Printf("%s    %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
828 829
		}
		for j := range stack[i].Locals {
830
			fmt.Printf("%s    %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
831
		}
A
aarzilli 已提交
832 833 834
	}
}

835
func printcontext(t *Term, state *api.DebuggerState) error {
836 837 838 839 840 841 842 843 844
	for i := range state.Threads {
		if (state.CurrentThread != nil) && (state.Threads[i].ID == state.CurrentThread.ID) {
			continue
		}
		if state.Threads[i].Breakpoint != nil {
			printcontextThread(t, state.Threads[i])
		}
	}

D
Dan Mace 已提交
845 846 847 848 849 850
	if state.CurrentThread == nil {
		fmt.Println("No current thread available")
		return nil
	}
	if len(state.CurrentThread.File) == 0 {
		fmt.Printf("Stopped at: 0x%x\n", state.CurrentThread.PC)
851
		t.Println("=>", "no source available")
D
Dan Mace 已提交
852 853
		return nil
	}
854 855 856 857 858

	printcontextThread(t, state.CurrentThread)

	if state.CurrentThread.Breakpoint == nil || !state.CurrentThread.Breakpoint.Tracepoint {
		return printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true)
D
Derek Parker 已提交
859
	}
860 861
	return nil
}
862

863 864 865 866 867 868 869 870 871 872 873 874 875
func printcontextThread(t *Term, th *api.Thread) {
	fn := th.Function

	if th.Breakpoint == nil {
		fmt.Printf("> %s() %s:%d\n", fn.Name, ShortenFilePath(th.File), th.Line)
		return
	}

	args := ""
	if th.Breakpoint.Tracepoint && fn != nil {
		var arg []string
		for _, ar := range fn.Args {
			arg = append(arg, ar.SinglelineString())
D
Derek Parker 已提交
876
		}
877 878 879 880 881 882 883 884 885 886 887 888
		args = strings.Join(arg, ", ")
	}

	if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
		fmt.Printf("> %s(%s) %s:%d (hits goroutine(%d):%d total:%d)\n",
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.GoroutineID,
			hitCount,
			th.Breakpoint.TotalHitCount)
D
Derek Parker 已提交
889
	} else {
890 891 892 893 894 895
		fmt.Printf("> %s(%s) %s:%d (hits total:%d)\n",
			fn.Name,
			args,
			ShortenFilePath(th.File),
			th.Line,
			th.Breakpoint.TotalHitCount)
D
Dan Mace 已提交
896 897
	}

898 899
	if th.BreakpointInfo != nil {
		bpi := th.BreakpointInfo
A
aarzilli 已提交
900 901

		if bpi.Goroutine != nil {
902
			writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t")
A
aarzilli 已提交
903 904
		}

905 906 907 908 909 910
		if len(bpi.Variables) > 0 {
			ss := make([]string, len(bpi.Variables))
			for i, v := range bpi.Variables {
				ss[i] = fmt.Sprintf("%s: %s", v.Name, v.MultilineString(""))
			}
			fmt.Printf("\t%s\n", strings.Join(ss, ", "))
A
aarzilli 已提交
911 912 913 914 915 916 917
		}

		if bpi.Stacktrace != nil {
			fmt.Printf("\tStack:\n")
			printStack(bpi.Stacktrace, "\t\t")
		}
	}
918 919
}

920
func printfile(t *Term, filename string, line int, showArrow bool) error {
921
	file, err := os.Open(filename)
D
Dan Mace 已提交
922 923 924 925 926
	if err != nil {
		return err
	}
	defer file.Close()

927
	buf := bufio.NewScanner(file)
928
	l := line
D
Dan Mace 已提交
929
	for i := 1; i < l-5; i++ {
930 931
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
932 933 934
		}
	}

935 936 937 938 939 940
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
941 942
		if !buf.Scan() {
			return nil
D
Dan Mace 已提交
943 944
		}

945
		var prefix string
946
		if showArrow {
947
			prefix = "  "
948
			if i == l {
949
				prefix = "=>"
950
			}
D
Dan Mace 已提交
951 952
		}

953 954
		prefix = fmt.Sprintf("%s%4d:\t", prefix, i)
		t.Println(prefix, buf.Text())
D
Dan Mace 已提交
955 956 957
	}
	return nil
}
D
Derek Parker 已提交
958

D
Derek Parker 已提交
959 960
// ExitRequestError is returned when the user
// exits Delve.
D
Derek Parker 已提交
961 962 963 964 965 966
type ExitRequestError struct{}

func (ere ExitRequestError) Error() string {
	return ""
}

967
func exitCommand(t *Term, args string) error {
D
Derek Parker 已提交
968 969
	return ExitRequestError{}
}
970

D
Derek Parker 已提交
971 972
// ShortenFilePath take a full file path and attempts to shorten
// it by replacing the current directory to './'.
973
func ShortenFilePath(fullPath string) string {
974 975 976
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}
977

D
Derek Parker 已提交
978
func (c *Commands) executeFile(t *Term, name string) error {
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
	fh, err := os.Open(name)
	if err != nil {
		return err
	}
	defer fh.Close()

	scanner := bufio.NewScanner(fh)
	lineno := 0
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		lineno++

		if line == "" || line[0] == '#' {
			continue
		}

		cmdstr, args := parseCommand(line)
D
Derek Parker 已提交
996
		cmd := c.Find(cmdstr)
997
		err := cmd(t, args)
998 999 1000 1001 1002 1003 1004 1005

		if err != nil {
			fmt.Printf("%s:%d: %v\n", name, lineno, err)
		}
	}

	return scanner.Err()
}