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

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
D
Derek Parker 已提交
14
	"text/tabwriter"
D
Dan Mace 已提交
15 16 17

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/api"
18
	"github.com/derekparker/delve/service/debugger"
D
Dan Mace 已提交
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
)

type cmdfunc func(client service.Client, args ...string) error

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
}

type Commands struct {
	cmds    []command
	lastCmd cmdfunc
	client  service.Client
}

// Returns a Commands struct with default commands defined.
func DebugCommands(client service.Client) *Commands {
	c := &Commands{client: client}

	c.cmds = []command{
		{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
51
		{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <linespec> [-stack <n>|-goroutine|<variable name>]*"},
D
Derek Parker 已提交
52
		{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
D
Derek Parker 已提交
53
		{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
D
Dan Mace 已提交
54 55 56 57
		{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 已提交
58
		{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified thread."},
D
Dan Mace 已提交
59 60 61 62 63
		{aliases: []string{"clear"}, cmdFn: clear, helpMsg: "Deletes breakpoint."},
		{aliases: []string{"clearall"}, cmdFn: clearAll, helpMsg: "Deletes all breakpoints."},
		{aliases: []string{"goroutines"}, cmdFn: goroutines, helpMsg: "Print out info for every goroutine."},
		{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
		{aliases: []string{"print", "p"}, cmdFn: printVar, helpMsg: "Evaluate a variable."},
D
Derek Parker 已提交
64 65 66 67 68 69
		{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."},
		{aliases: []string{"args"}, cmdFn: filterSortAndOutput(args), helpMsg: "Print function arguments, optionally filtered by a regexp."},
		{aliases: []string{"locals"}, cmdFn: filterSortAndOutput(locals), helpMsg: "Print function locals, optionally filtered by a regexp."},
		{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 已提交
70
		{aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."},
71
		{aliases: []string{"stack", "bt"}, cmdFn: stackCommand, helpMsg: "stack [<depth> [<goroutine id>]]. Prints stack."},
72
		{aliases: []string{"list", "ls"}, cmdFn: listCommand, helpMsg: "list <linespec>.  Show source around current point or provided linespec."},
D
Dan Mace 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
	}

	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 已提交
92
// If it cannot find the command it will default to noCmdAvailable().
D
Dan Mace 已提交
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
// 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
}

func CommandFunc(fn func() error) cmdfunc {
	return func(client service.Client, args ...string) error {
		return fn()
	}
}

func noCmdAvailable(client service.Client, args ...string) error {
D
Derek Parker 已提交
120
	return fmt.Errorf("command not available")
D
Dan Mace 已提交
121 122 123 124 125 126 127 128
}

func nullCommand(client service.Client, args ...string) error {
	return nil
}

func (c *Commands) help(client service.Client, args ...string) error {
	fmt.Println("The following commands are available:")
D
Derek Parker 已提交
129 130
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 0, '-', 0)
D
Dan Mace 已提交
131
	for _, cmd := range c.cmds {
D
Derek Parker 已提交
132 133 134 135 136
		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 已提交
137
	}
D
Derek Parker 已提交
138
	return w.Flush()
D
Dan Mace 已提交
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
}

func threads(client service.Client, args ...string) error {
	threads, err := client.ListThreads()
	if err != nil {
		return err
	}
	state, err := client.GetState()
	if err != nil {
		return err
	}
	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",
157
				prefix, th.ID, th.PC, shortenFilePath(th.File),
D
Dan Mace 已提交
158 159
				th.Line, th.Function.Name)
		} else {
160
			fmt.Printf("%sThread %d at %s:%d\n", prefix, th.ID, shortenFilePath(th.File), th.Line)
D
Dan Mace 已提交
161 162 163 164 165 166
		}
	}
	return nil
}

func thread(client service.Client, args ...string) error {
167 168 169
	if len(args) == 0 {
		return fmt.Errorf("you must specify a thread")
	}
D
Dan Mace 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
	tid, err := strconv.Atoi(args[0])
	if err != nil {
		return err
	}
	oldState, err := client.GetState()
	if err != nil {
		return err
	}
	newState, err := client.SwitchThread(tid)
	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
}

func goroutines(client service.Client, args ...string) error {
	gs, err := client.ListGoroutines()
	if err != nil {
		return err
	}
	fmt.Printf("[%d goroutines]\n", len(gs))
	for _, g := range gs {
A
aarzilli 已提交
202
		fmt.Printf("Goroutine %s\n", formatGoroutine(g))
D
Dan Mace 已提交
203 204 205 206
	}
	return nil
}

A
aarzilli 已提交
207 208 209 210 211
func formatGoroutine(g *api.Goroutine) string {
	fname := ""
	if g.Function != nil {
		fname = g.Function.Name
	}
212
	return fmt.Sprintf("%d - %s:%d %s (%#v)", g.ID, shortenFilePath(g.File), g.Line, fname, g.PC)
A
aarzilli 已提交
213 214
}

D
Derek Parker 已提交
215 216 217 218 219 220 221 222
func restart(client service.Client, args ...string) error {
	if err := client.Restart(); err != nil {
		return err
	}
	fmt.Println("Process restarted with PID", client.ProcessPid())
	return nil
}

D
Dan Mace 已提交
223
func cont(client service.Client, args ...string) error {
D
Derek Parker 已提交
224 225
	stateChan := client.Continue()
	for state := range stateChan {
A
aarzilli 已提交
226 227 228 229
		if state.Err != nil {
			return state.Err
		}
		printcontext(state)
D
Dan Mace 已提交
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
	}
	return nil
}

func step(client service.Client, args ...string) error {
	state, err := client.Step()
	if err != nil {
		return err
	}
	printcontext(state)
	return nil
}

func next(client service.Client, args ...string) error {
	state, err := client.Next()
	if err != nil {
		return err
	}
	printcontext(state)
	return nil
}

func clear(client service.Client, args ...string) error {
	if len(args) == 0 {
D
Derek Parker 已提交
254
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
255 256 257 258 259
	}
	id, err := strconv.Atoi(args[0])
	if err != nil {
		return err
	}
D
Derek Parker 已提交
260
	bp, err := client.ClearBreakpoint(id)
D
Dan Mace 已提交
261 262 263
	if err != nil {
		return err
	}
264
	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 已提交
265 266 267 268
	return nil
}

func clearAll(client service.Client, args ...string) error {
D
Derek Parker 已提交
269
	breakPoints, err := client.ListBreakpoints()
D
Dan Mace 已提交
270 271 272 273
	if err != nil {
		return err
	}
	for _, bp := range breakPoints {
D
Derek Parker 已提交
274
		_, err := client.ClearBreakpoint(bp.ID)
D
Dan Mace 已提交
275
		if err != nil {
276
			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 已提交
277
		}
278
		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 已提交
279 280 281 282
	}
	return nil
}

D
Derek Parker 已提交
283
type ById []*api.Breakpoint
D
Dan Mace 已提交
284 285 286 287 288 289

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 }

func breakpoints(client service.Client, args ...string) error {
D
Derek Parker 已提交
290
	breakPoints, err := client.ListBreakpoints()
D
Dan Mace 已提交
291 292 293 294 295
	if err != nil {
		return err
	}
	sort.Sort(ById(breakPoints))
	for _, bp := range breakPoints {
A
aarzilli 已提交
296 297 298 299
		thing := "Breakpoint"
		if bp.Tracepoint {
			thing = "Tracepoint"
		}
300
		fmt.Printf("%s %d at %#v %s:%d\n", thing, bp.ID, bp.Addr, shortenFilePath(bp.File), bp.Line)
A
aarzilli 已提交
301 302 303 304 305 306 307 308 309

		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 已提交
310 311
		for i := range bp.Variables {
			attrs = append(attrs, bp.Variables[i])
A
aarzilli 已提交
312 313 314 315
		}
		if len(attrs) > 0 {
			fmt.Printf("\t%s\n", strings.Join(attrs, " "))
		}
D
Dan Mace 已提交
316 317 318 319
	}
	return nil
}

D
Derek Parker 已提交
320
func setBreakpoint(client service.Client, tracepoint bool, args ...string) error {
A
aarzilli 已提交
321 322
	if len(args) < 1 {
		return fmt.Errorf("address required, specify either a function name or <file:line>")
D
Dan Mace 已提交
323
	}
D
Derek Parker 已提交
324
	requestedBp := &api.Breakpoint{}
D
Dan Mace 已提交
325

A
aarzilli 已提交
326 327 328 329 330 331 332 333 334 335 336 337
	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 已提交
338
			requestedBp.Variables = append(requestedBp.Variables, args[i])
A
aarzilli 已提交
339 340 341 342
		}
	}

	requestedBp.Tracepoint = tracepoint
343
	locs, err := client.FindLocation(args[0])
D
Dan Mace 已提交
344 345 346
	if err != nil {
		return err
	}
A
aarzilli 已提交
347 348 349 350
	thing := "Breakpoint"
	if tracepoint {
		thing = "Tracepoint"
	}
351 352 353 354 355 356 357 358
	for _, loc := range locs {
		requestedBp.Addr = loc.PC

		bp, err := client.CreateBreakpoint(requestedBp)
		if err != nil {
			return err
		}

359
		fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, shortenFilePath(bp.File), bp.Line)
360
	}
D
Dan Mace 已提交
361 362 363
	return nil
}

A
aarzilli 已提交
364
func breakpoint(client service.Client, args ...string) error {
D
Derek Parker 已提交
365
	return setBreakpoint(client, false, args...)
A
aarzilli 已提交
366 367 368
}

func tracepoint(client service.Client, args ...string) error {
D
Derek Parker 已提交
369
	return setBreakpoint(client, true, args...)
A
aarzilli 已提交
370 371
}

D
Dan Mace 已提交
372 373
func printVar(client service.Client, args ...string) error {
	if len(args) == 0 {
D
Derek Parker 已提交
374
		return fmt.Errorf("not enough arguments")
D
Dan Mace 已提交
375
	}
376
	val, err := client.EvalVariable(args[0])
D
Dan Mace 已提交
377 378 379 380 381 382 383
	if err != nil {
		return err
	}
	fmt.Println(val.Value)
	return nil
}

D
Derek Parker 已提交
384 385 386 387 388 389
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 已提交
390 391
	data := make([]string, 0, len(vars))
	for _, v := range vars {
D
Derek Parker 已提交
392
		if reg == nil || reg.Match([]byte(v.Name)) {
D
Dan Mace 已提交
393 394 395 396 397 398
			data = append(data, fmt.Sprintf("%s = %s", v.Name, v.Value))
		}
	}
	return data
}

D
Derek Parker 已提交
399 400 401
func sources(client service.Client, filter string) ([]string, error) {
	return client.ListSources(filter)
}
D
Dan Mace 已提交
402

D
Derek Parker 已提交
403 404 405
func funcs(client service.Client, filter string) ([]string, error) {
	return client.ListFunctions(filter)
}
D
Dan Mace 已提交
406

D
Derek Parker 已提交
407 408 409 410 411 412 413
func args(client service.Client, filter string) ([]string, error) {
	vars, err := client.ListFunctionArgs()
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
414

D
Derek Parker 已提交
415 416 417 418 419 420 421
func locals(client service.Client, filter string) ([]string, error) {
	locals, err := client.ListLocalVariables()
	if err != nil {
		return nil, err
	}
	return filterVariables(locals, filter), nil
}
D
Dan Mace 已提交
422

D
Derek Parker 已提交
423 424 425 426 427 428 429
func vars(client service.Client, filter string) ([]string, error) {
	vars, err := client.ListPackageVariables(filter)
	if err != nil {
		return nil, err
	}
	return filterVariables(vars, filter), nil
}
D
Dan Mace 已提交
430

D
Derek Parker 已提交
431 432 433 434 435 436 437 438
func regs(client service.Client, args ...string) error {
	regs, err := client.ListRegisters()
	if err != nil {
		return err
	}
	fmt.Println(regs)
	return nil
}
439

D
Derek Parker 已提交
440 441 442 443 444 445 446 447
func filterSortAndOutput(fn func(client service.Client, filter string) ([]string, error)) cmdfunc {
	return func(client service.Client, args ...string) error {
		var filter string
		if len(args) == 1 {
			if _, err := regexp.Compile(args[0]); err != nil {
				return fmt.Errorf("invalid filter argument: %s", err.Error())
			}
			filter = args[0]
D
Dan Mace 已提交
448
		}
D
Derek Parker 已提交
449
		data, err := fn(client, filter)
D
Dan Mace 已提交
450 451 452
		if err != nil {
			return err
		}
D
Derek Parker 已提交
453 454 455
		sort.Sort(sort.StringSlice(data))
		for _, d := range data {
			fmt.Println(d)
D
Dan Mace 已提交
456
		}
D
Derek Parker 已提交
457
		return nil
D
Dan Mace 已提交
458 459 460
	}
}

A
aarzilli 已提交
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
func stackCommand(client service.Client, args ...string) error {
	var err error

	goroutineid := -1
	depth := 10

	switch len(args) {
	case 0:
		// nothing to do
	case 2:
		goroutineid, err = strconv.Atoi(args[1])
		if err != nil {
			return fmt.Errorf("Wrong argument: expected integer")
		}
		fallthrough
	case 1:
		depth, err = strconv.Atoi(args[0])
		if err != nil {
			return fmt.Errorf("Wrong argument: expected integer")
		}

	default:
		return fmt.Errorf("Wrong number of arguments to stack")
	}

	stack, err := client.Stacktrace(goroutineid, depth)
	if err != nil {
		return err
	}
A
aarzilli 已提交
490 491 492 493
	printStack(stack, "")
	return nil
}

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
func listCommand(client service.Client, args ...string) error {
	if len(args) == 0 {
		state, err := client.GetState()
		if err != nil {
			return err
		}
		printcontext(state)
		return nil
	}

	locs, err := client.FindLocation(args[0])
	if err != nil {
		return err
	}
	if len(locs) > 1 {
		return debugger.AmbiguousLocationError{Location: args[0], CandidatesLocation: locs}
	}
	printfile(locs[0].File, locs[0].Line, false)
	return nil
}

A
aarzilli 已提交
515
func printStack(stack []api.Location, ind string) {
A
aarzilli 已提交
516 517 518 519 520
	for i := range stack {
		name := "(nil)"
		if stack[i].Function != nil {
			name = stack[i].Function.Name
		}
521
		fmt.Printf("%s%d. %s %s:%d (%#v)\n", ind, i, name, shortenFilePath(stack[i].File), stack[i].Line, stack[i].PC)
A
aarzilli 已提交
522 523 524
	}
}

D
Dan Mace 已提交
525 526 527 528 529 530 531 532 533 534
func printcontext(state *api.DebuggerState) error {
	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)
		fmt.Printf("\033[34m=>\033[0m    no source available\n")
		return nil
	}
D
Derek Parker 已提交
535
	var fn *api.Function
D
Dan Mace 已提交
536
	if state.CurrentThread.Function != nil {
D
Derek Parker 已提交
537 538 539 540 541 542 543
		fn = state.CurrentThread.Function
	}
	if state.Breakpoint != nil && state.Breakpoint.Tracepoint {
		var args []string
		for _, arg := range state.CurrentThread.Function.Args {
			args = append(args, arg.Value)
		}
544
		fmt.Printf("> %s(%s) %s:%d\n", fn.Name, strings.Join(args, ", "), shortenFilePath(state.CurrentThread.File), state.CurrentThread.Line)
D
Derek Parker 已提交
545
	} else {
546
		fmt.Printf("> %s() %s:%d\n", fn.Name, shortenFilePath(state.CurrentThread.File), state.CurrentThread.Line)
D
Dan Mace 已提交
547 548
	}

A
aarzilli 已提交
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
	if state.BreakpointInfo != nil {
		bpi := state.BreakpointInfo

		if bpi.Goroutine != nil {
			fmt.Printf("\tGoroutine %s\n", formatGoroutine(bpi.Goroutine))
		}

		ss := make([]string, len(bpi.Variables))
		for i, v := range bpi.Variables {
			ss[i] = fmt.Sprintf("%s: <%v>", v.Name, v.Value)
		}
		fmt.Printf("\t%s\n", strings.Join(ss, ", "))

		if bpi.Stacktrace != nil {
			fmt.Printf("\tStack:\n")
			printStack(bpi.Stacktrace, "\t\t")
		}
	}
	if state.Breakpoint != nil && state.Breakpoint.Tracepoint {
		return nil
	}
570 571 572 573 574
	return printfile(state.CurrentThread.File, state.CurrentThread.Line, true)
}

func printfile(filename string, line int, showArrow bool) error {
	file, err := os.Open(filename)
D
Dan Mace 已提交
575 576 577 578 579
	if err != nil {
		return err
	}
	defer file.Close()

580
	var context []string
D
Dan Mace 已提交
581
	buf := bufio.NewReader(file)
582
	l := line
D
Dan Mace 已提交
583 584 585 586 587 588 589
	for i := 1; i < l-5; i++ {
		_, err := buf.ReadString('\n')
		if err != nil && err != io.EOF {
			return err
		}
	}

590 591 592 593 594 595
	s := l - 5
	if s < 1 {
		s = 1
	}

	for i := s; i <= l+5; i++ {
D
Dan Mace 已提交
596 597 598 599 600 601 602 603 604 605 606
		line, err := buf.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				return err
			}

			if err == io.EOF {
				break
			}
		}

607 608 609 610 611 612
		var arrow string
		if showArrow {
			arrow = "  "
			if i == l {
				arrow = "=>"
			}
D
Dan Mace 已提交
613 614
		}

615 616 617 618 619 620 621
		var lineNum string
		if i < 10 {
			lineNum = fmt.Sprintf("\033[34m%s  %d\033[0m:\t", arrow, i)
		} else {
			lineNum = fmt.Sprintf("\033[34m%s %d\033[0m:\t", arrow, i)
		}
		context = append(context, lineNum+line)
D
Dan Mace 已提交
622 623 624 625 626 627
	}

	fmt.Println(strings.Join(context, ""))

	return nil
}
D
Derek Parker 已提交
628 629 630 631 632 633 634 635 636 637

type ExitRequestError struct{}

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

func exitCommand(client service.Client, args ...string) error {
	return ExitRequestError{}
}
638 639 640 641 642

func shortenFilePath(fullPath string) string {
	workingDir, _ := os.Getwd()
	return strings.Replace(fullPath, workingDir, ".", 1)
}