From 9d3d9d10acb85b53e46ea45c1edec1a59fc75529 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 31 May 2018 08:36:52 +0100 Subject: [PATCH] o debugger - refactored user interface types and functions - now in ui package - sketched in tab completion --- debugger/breakpoints.go | 11 +- debugger/colorterm/colorterm.go | 224 +----------------------- debugger/colorterm/input.go | 193 ++++++++++++++++++++ debugger/colorterm/output.go | 42 +++++ debugger/debugger.go | 52 +++--- debugger/machineinfo.go | 4 +- debugger/print.go | 28 +-- debugger/script.go | 7 +- debugger/traps.go | 9 +- debugger/{ui.go => ui/plainterminal.go} | 25 +-- debugger/ui/printprofile.go | 24 +++ debugger/ui/tabcompletion.go | 6 + debugger/ui/ui.go | 21 +++ gopher2600.go | 5 +- 14 files changed, 352 insertions(+), 299 deletions(-) create mode 100644 debugger/colorterm/input.go create mode 100644 debugger/colorterm/output.go rename debugger/{ui.go => ui/plainterminal.go} (63%) create mode 100644 debugger/ui/printprofile.go create mode 100644 debugger/ui/tabcompletion.go create mode 100644 debugger/ui/ui.go diff --git a/debugger/breakpoints.go b/debugger/breakpoints.go index 9bb7ac92..70df6500 100644 --- a/debugger/breakpoints.go +++ b/debugger/breakpoints.go @@ -2,6 +2,7 @@ package debugger import ( "fmt" + "gopher2600/debugger/ui" "strconv" ) @@ -64,7 +65,7 @@ func (bp *breakpoints) check() bool { // make sure that we're not breaking on an ignore state bv, prs := bp.ignoredBreakerStates[bp.breaks[i].target] if !prs || prs && bp.breaks[i].target.ToInt() != bv { - bp.dbg.print(Feedback, "break on %s=%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value) + bp.dbg.print(ui.Feedback, "break on %s=%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value) broken = true } } @@ -86,10 +87,10 @@ func (bp *breakpoints) check() bool { func (bp breakpoints) list() { if len(bp.breaks) == 0 { - bp.dbg.print(Feedback, "no breakpoints") + bp.dbg.print(ui.Feedback, "no breakpoints") } else { for i := range bp.breaks { - bp.dbg.print(Feedback, "%s->%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value) + bp.dbg.print(ui.Feedback, "%s->%d", bp.breaks[i].target.ShortLabel(), bp.breaks[i].value) } } } @@ -121,7 +122,7 @@ func (bp *breakpoints) parseBreakpoint(parts []string) error { for _, mv := range bp.breaks { if mv.target == tgt && mv.value == int(val) { addNewBreak = false - bp.dbg.print(Feedback, "breakpoint already exists") + bp.dbg.print(ui.Feedback, "breakpoint already exists") break // for loop } } @@ -140,7 +141,7 @@ func (bp *breakpoints) parseBreakpoint(parts []string) error { switch parts[i] { case "CLEAR": bp.clear() - bp.dbg.print(Feedback, "breakpoints cleared") + bp.dbg.print(ui.Feedback, "breakpoints cleared") return nil case "LIST": bp.list() diff --git a/debugger/colorterm/colorterm.go b/debugger/colorterm/colorterm.go index e522218b..f57852a5 100644 --- a/debugger/colorterm/colorterm.go +++ b/debugger/colorterm/colorterm.go @@ -2,12 +2,9 @@ package colorterm import ( "bufio" - "gopher2600/debugger" - "gopher2600/debugger/colorterm/ansi" "gopher2600/debugger/colorterm/easyterm" + "gopher2600/debugger/ui" "os" - "unicode" - "unicode/utf8" ) // ColorTerminal implements debugger UI interface with a basic ANSI terminal @@ -16,6 +13,8 @@ type ColorTerminal struct { reader *bufio.Reader commandHistory []command + + tabCompleter ui.TabCompleter } type command struct { @@ -42,217 +41,8 @@ func (ct *ColorTerminal) CleanUp() { ct.Terminal.CleanUp() } -// UserPrint implementation for debugger.UI interface -func (ct *ColorTerminal) UserPrint(pp debugger.PrintProfile, s string, a ...interface{}) { - if pp != debugger.Input { - ct.Print("\r") - } - - switch pp { - case debugger.CPUStep: - ct.Print(ansi.PenColor["yellow"]) - case debugger.VideoStep: - ct.Print(ansi.DimPens["yellow"]) - case debugger.MachineInfo: - ct.Print(ansi.PenColor["cyan"]) - case debugger.Error: - ct.Print(ansi.PenColor["red"]) - ct.Print(ansi.PenColor["bold"]) - ct.Print("* ") - ct.Print(ansi.NormalPen) - ct.Print(ansi.PenColor["red"]) - case debugger.Feedback: - ct.Print(ansi.DimPens["white"]) - case debugger.Script: - ct.Print("> ") - case debugger.Prompt: - ct.Print(ansi.PenStyles["bold"]) - } - - ct.Print(s, a...) - ct.Print(ansi.NormalPen) - - // add a newline if print profile is anything other than prompt - if pp != debugger.Prompt && pp != debugger.Input { - ct.Print("\n") - } -} - -// UserRead implementation for debugger.UI interface -func (ct *ColorTerminal) UserRead(input []byte, prompt string) (int, error) { - ct.RawMode() - defer ct.CanonicalMode() - - // er is used to store encoded runes (length of 4 should be enough) - er := make([]byte, 4) - - n := 0 - cursor := 0 - history := len(ct.commandHistory) - - // buffInput is used to store the latest input when we scroll through - // history - we don't want to lose what we've typed in case the user wants - // to resume where we left off - buffInput := make([]byte, cap(input)) - buffN := 0 - - // the method for cursor placement is as follows: - // 1. for each iteration in the loop - // 2. store current cursor position - // 3. clear the current line - // 4. output the prompt - // 5. output the input buffer - // 6. restore the cursor position - // - // for this to work we need to place the cursor in it's initial position, - ct.Print("\r%s", ansi.CursorMove(len(prompt))) - - for { - ct.Print(ansi.CursorStore) - ct.UserPrint(debugger.Prompt, "%s%s", ansi.ClearLine, prompt) - ct.UserPrint(debugger.Input, string(input[:n])) - ct.Print(ansi.CursorRestore) - - r, _, err := ct.reader.ReadRune() - if err != nil { - return n, err - } - - switch r { - case easyterm.KeyCtrlC: - // CTRL-C - ct.Print("\n") - return n + 1, &debugger.UserInterrupt{Message: "user interrupt: CTRL-C"} - - case easyterm.KeyTab: - - case easyterm.KeyCarriageReturn: - // CARRIAGE RETURN - - // check to see if input is the same as the last history entry - newEntry := false - if n > 0 { - newEntry = true - if len(ct.commandHistory) > 0 { - lastHistoryEntry := ct.commandHistory[len(ct.commandHistory)-1].input - if len(lastHistoryEntry) == n { - newEntry = false - for i := 0; i < n; i++ { - if input[i] != lastHistoryEntry[i] { - newEntry = true - break - } - } - } - } - } - - // if input is not the same as the last history entry then append a - // new entry to the history list - if newEntry { - nh := make([]byte, n) - copy(nh, input[:n]) - ct.commandHistory = append(ct.commandHistory, command{input: nh}) - } - - ct.Print("\n") - return n + 1, nil - - case easyterm.KeyEsc: - // ESCAPE SEQUENCE BEGIN - r, _, err := ct.reader.ReadRune() - if err != nil { - return n, err - } - switch r { - case easyterm.EscCursor: - // CURSOR KEY - r, _, err := ct.reader.ReadRune() - if err != nil { - return n, err - } - - switch r { - case easyterm.CursorUp: - // move up through command history - if len(ct.commandHistory) > 0 { - // if we're at the end of the command history then store - // the current input in buffInput for possible later editing - if history == len(ct.commandHistory) { - copy(buffInput, input[:n]) - buffN = n - } - - if history > 0 { - history-- - copy(input, ct.commandHistory[history].input) - n = len(ct.commandHistory[history].input) - ct.Print(ansi.CursorMove(n - cursor)) - cursor = n - } - } - case easyterm.CursorDown: - // move down through command history - if len(ct.commandHistory) > 0 { - if history < len(ct.commandHistory)-1 { - history++ - copy(input, ct.commandHistory[history].input) - n = len(ct.commandHistory[history].input) - ct.Print(ansi.CursorMove(n - cursor)) - cursor = n - } else if history == len(ct.commandHistory)-1 { - history++ - copy(input, buffInput) - n = buffN - ct.Print(ansi.CursorMove(n - cursor)) - cursor = n - } - } - case easyterm.CursorForward: - // move forward through current command input - if cursor < n { - ct.Print(ansi.CursorForwardOne) - cursor++ - } - case easyterm.CursorBackward: - // move backward through current command input - if cursor > 0 { - ct.Print(ansi.CursorBackwardOne) - cursor-- - } - - case easyterm.EscDelete: - // DELETE - if cursor < n { - copy(input[cursor:], input[cursor+1:]) - cursor-- - n-- - history = len(ct.commandHistory) - } - } - } - - case easyterm.KeyBackspace: - // BACKSPACE - if cursor > 0 { - copy(input[cursor-1:], input[cursor:]) - ct.Print(ansi.CursorBackwardOne) - cursor-- - n-- - history = len(ct.commandHistory) - } - - default: - if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsSpace(r) { - ct.Print("%c", r) - m := utf8.EncodeRune(er, r) - copy(input[cursor+m:], input[cursor:]) - copy(input[cursor:], er[:m]) - cursor++ - n += m - history = len(ct.commandHistory) - } - } - - } +// RegisterTabCompleter adds an implementation of TabCompleter to the +// ColorTerminal +func (ct *ColorTerminal) RegisterTabCompleter(tc ui.TabCompleter) { + ct.tabCompleter = tc } diff --git a/debugger/colorterm/input.go b/debugger/colorterm/input.go new file mode 100644 index 00000000..b79787f6 --- /dev/null +++ b/debugger/colorterm/input.go @@ -0,0 +1,193 @@ +package colorterm + +import ( + "gopher2600/debugger/colorterm/ansi" + "gopher2600/debugger/colorterm/easyterm" + "gopher2600/debugger/ui" + "unicode" + "unicode/utf8" +) + +// UserRead is the top level input function +func (ct *ColorTerminal) UserRead(input []byte, prompt string) (int, error) { + ct.RawMode() + defer ct.CanonicalMode() + + // er is used to store encoded runes (length of 4 should be enough) + er := make([]byte, 4) + + n := 0 + cursor := 0 + history := len(ct.commandHistory) + + // buffInput is used to store the latest input when we scroll through + // history - we don't want to lose what we've typed in case the user wants + // to resume where we left off + buffInput := make([]byte, cap(input)) + buffN := 0 + + // the method for cursor placement is as follows: + // 1. for each iteration in the loop + // 2. store current cursor position + // 3. clear the current line + // 4. output the prompt + // 5. output the input buffer + // 6. restore the cursor position + // + // for this to work we need to place the cursor in it's initial position, + ct.Print("\r%s", ansi.CursorMove(len(prompt))) + + for { + ct.Print(ansi.CursorStore) + ct.UserPrint(ui.Prompt, "%s%s", ansi.ClearLine, prompt) + ct.UserPrint(ui.Input, string(input[:n])) + ct.Print(ansi.CursorRestore) + + r, _, err := ct.reader.ReadRune() + if err != nil { + return n, err + } + + switch r { + case easyterm.KeyCtrlC: + // CTRL-C + ct.Print("\n") + return n + 1, &ui.UserInterrupt{Message: "user interrupt: CTRL-C"} + + case easyterm.KeyTab: + if ct.tabCompleter != nil { + s := ct.tabCompleter.GuessWord(string(input[:n])) + n = len(s) + copy(input, []byte(s)) + } + + case easyterm.KeyCarriageReturn: + // CARRIAGE RETURN + + // check to see if input is the same as the last history entry + newEntry := false + if n > 0 { + newEntry = true + if len(ct.commandHistory) > 0 { + lastHistoryEntry := ct.commandHistory[len(ct.commandHistory)-1].input + if len(lastHistoryEntry) == n { + newEntry = false + for i := 0; i < n; i++ { + if input[i] != lastHistoryEntry[i] { + newEntry = true + break + } + } + } + } + } + + // if input is not the same as the last history entry then append a + // new entry to the history list + if newEntry { + nh := make([]byte, n) + copy(nh, input[:n]) + ct.commandHistory = append(ct.commandHistory, command{input: nh}) + } + + ct.Print("\n") + return n + 1, nil + + case easyterm.KeyEsc: + // ESCAPE SEQUENCE BEGIN + r, _, err := ct.reader.ReadRune() + if err != nil { + return n, err + } + switch r { + case easyterm.EscCursor: + // CURSOR KEY + r, _, err := ct.reader.ReadRune() + if err != nil { + return n, err + } + + switch r { + case easyterm.CursorUp: + // move up through command history + if len(ct.commandHistory) > 0 { + // if we're at the end of the command history then store + // the current input in buffInput for possible later editing + if history == len(ct.commandHistory) { + copy(buffInput, input[:n]) + buffN = n + } + + if history > 0 { + history-- + copy(input, ct.commandHistory[history].input) + n = len(ct.commandHistory[history].input) + ct.Print(ansi.CursorMove(n - cursor)) + cursor = n + } + } + case easyterm.CursorDown: + // move down through command history + if len(ct.commandHistory) > 0 { + if history < len(ct.commandHistory)-1 { + history++ + copy(input, ct.commandHistory[history].input) + n = len(ct.commandHistory[history].input) + ct.Print(ansi.CursorMove(n - cursor)) + cursor = n + } else if history == len(ct.commandHistory)-1 { + history++ + copy(input, buffInput) + n = buffN + ct.Print(ansi.CursorMove(n - cursor)) + cursor = n + } + } + case easyterm.CursorForward: + // move forward through current command input + if cursor < n { + ct.Print(ansi.CursorForwardOne) + cursor++ + } + case easyterm.CursorBackward: + // move backward through current command input + if cursor > 0 { + ct.Print(ansi.CursorBackwardOne) + cursor-- + } + + case easyterm.EscDelete: + // DELETE + if cursor < n { + copy(input[cursor:], input[cursor+1:]) + cursor-- + n-- + history = len(ct.commandHistory) + } + } + } + + case easyterm.KeyBackspace: + // BACKSPACE + if cursor > 0 { + copy(input[cursor-1:], input[cursor:]) + ct.Print(ansi.CursorBackwardOne) + cursor-- + n-- + history = len(ct.commandHistory) + } + + default: + if unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsSpace(r) { + ct.Print("%c", r) + m := utf8.EncodeRune(er, r) + copy(input[cursor+m:], input[cursor:]) + copy(input[cursor:], er[:m]) + cursor++ + n += m + history = len(ct.commandHistory) + } + } + + } +} diff --git a/debugger/colorterm/output.go b/debugger/colorterm/output.go new file mode 100644 index 00000000..c8ef2090 --- /dev/null +++ b/debugger/colorterm/output.go @@ -0,0 +1,42 @@ +package colorterm + +import ( + "gopher2600/debugger/colorterm/ansi" + "gopher2600/debugger/ui" +) + +// UserPrint is the top level output function +func (ct *ColorTerminal) UserPrint(pp ui.PrintProfile, s string, a ...interface{}) { + if pp != ui.Input { + ct.Print("\r") + } + + switch pp { + case ui.CPUStep: + ct.Print(ansi.PenColor["yellow"]) + case ui.VideoStep: + ct.Print(ansi.DimPens["yellow"]) + case ui.MachineInfo: + ct.Print(ansi.PenColor["cyan"]) + case ui.Error: + ct.Print(ansi.PenColor["red"]) + ct.Print(ansi.PenColor["bold"]) + ct.Print("* ") + ct.Print(ansi.NormalPen) + ct.Print(ansi.PenColor["red"]) + case ui.Feedback: + ct.Print(ansi.DimPens["white"]) + case ui.Script: + ct.Print("> ") + case ui.Prompt: + ct.Print(ansi.PenStyles["bold"]) + } + + ct.Print(s, a...) + ct.Print(ansi.NormalPen) + + // add a newline if print profile is anything other than prompt + if pp != ui.Prompt && pp != ui.Input { + ct.Print("\n") + } +} diff --git a/debugger/debugger.go b/debugger/debugger.go index 991ffb3a..e73079d9 100644 --- a/debugger/debugger.go +++ b/debugger/debugger.go @@ -2,6 +2,7 @@ package debugger import ( "fmt" + "gopher2600/debugger/ui" "gopher2600/hardware" "gopher2600/hardware/cpu" "gopher2600/television" @@ -39,7 +40,7 @@ type Debugger struct { inputloopVideoClock bool // step mode // user interface - ui UserInterface + ui ui.UserInterface uiSilent bool // controls whether UI is to remain silent } @@ -73,15 +74,15 @@ func NewDebugger() (*Debugger, error) { } // Start the main debugger sequence -func (dbg *Debugger) Start(ui UserInterface, filename string) error { +func (dbg *Debugger) Start(interf ui.UserInterface, filename string) error { // prepare user interface - if ui == nil { - dbg.ui = new(PlainTerminal) + if interf == nil { + dbg.ui = new(ui.PlainTerminal) if dbg.ui == nil { return fmt.Errorf("error allocationg memory for UI") } } else { - dbg.ui = ui + dbg.ui = interf } err := dbg.ui.Initialise() @@ -90,6 +91,8 @@ func (dbg *Debugger) Start(ui UserInterface, filename string) error { } defer dbg.ui.CleanUp() + dbg.ui.RegisterTabCompleter(dbg) + err = dbg.vcs.AttachCartridge(filename) if err != nil { return err @@ -123,7 +126,7 @@ func (dbg *Debugger) Start(ui UserInterface, filename string) error { // videoCycleInputLoop is a wrapper function to be used when calling vcs.Step() func (dbg *Debugger) videoCycleInputLoop(result *cpu.InstructionResult) error { if dbg.inputloopVideoClock { - dbg.print(VideoStep, "%v", result) + dbg.print(ui.VideoStep, "%v", result) } return dbg.inputLoop(false) } @@ -182,8 +185,8 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { n, err := dbg.ui.UserRead(dbg.input, prompt) if err != nil { switch err.(type) { - case *UserInterrupt: - dbg.print(Feedback, err.Error()) + case *ui.UserInterrupt: + dbg.print(ui.Feedback, err.Error()) return nil default: return err @@ -193,7 +196,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { // parse user input dbg.inputloopNext, err = dbg.parseInput(string(dbg.input[:n-1])) if err != nil { - dbg.print(Error, "%s", err) + dbg.print(ui.Error, "%s", err) } // prepare for next loop @@ -218,7 +221,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error { if err != nil { return err } - dbg.print(CPUStep, "%v", result) + dbg.print(ui.CPUStep, "%v", result) } else { return nil } @@ -302,7 +305,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { } else { if parts[1] == "OFF" { dbg.commandOnHalt = "" - dbg.print(Feedback, "no auto-command on halt") + dbg.print(ui.Feedback, "no auto-command on halt") return false, nil } @@ -320,10 +323,10 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { dbg.commandOnHaltStored = dbg.commandOnHalt } - dbg.print(Feedback, "auto-command on halt: %s", dbg.commandOnHalt) + dbg.print(ui.Feedback, "auto-command on halt: %s", dbg.commandOnHalt) case "MEMMAP": - dbg.print(MachineInfo, "%v", dbg.vcs.Mem.MemoryMap()) + dbg.print(ui.MachineInfo, "%v", dbg.vcs.Mem.MemoryMap()) case "QUIT": dbg.running = false @@ -333,7 +336,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { if err != nil { return false, err } - dbg.print(Feedback, "machine reset") + dbg.print(ui.Feedback, "machine reset") case "RUN": dbg.runUntilHalt = true @@ -367,21 +370,21 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { } else { stepMode = "cpu" } - dbg.print(Feedback, "step mode: %s", stepMode) + dbg.print(ui.Feedback, "step mode: %s", stepMode) case "TERSE": dbg.machineInfoVerbose = false - dbg.print(Feedback, "verbosity: terse") + dbg.print(ui.Feedback, "verbosity: terse") case "VERBOSE": dbg.machineInfoVerbose = true - dbg.print(Feedback, "verbosity: verbose") + dbg.print(ui.Feedback, "verbosity: verbose") case "VERBOSITY": if dbg.machineInfoVerbose { - dbg.print(Feedback, "verbosity: verbose") + dbg.print(ui.Feedback, "verbosity: verbose") } else { - dbg.print(Feedback, "verbosity: terse") + dbg.print(ui.Feedback, "verbosity: terse") } case "DEBUGGERSTATE": @@ -403,14 +406,14 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { for i := 1; i < len(parts); i++ { addr, err := strconv.ParseUint(parts[i], 0, 16) if err != nil { - dbg.print(Error, "bad argument to PEEK (%s)", parts[i]) + dbg.print(ui.Error, "bad argument to PEEK (%s)", parts[i]) continue } // peform peek val, mappedAddress, areaName, addressLabel, err := dbg.vcs.Mem.Peek(uint16(addr)) if err != nil { - dbg.print(Error, "%s", err) + dbg.print(ui.Error, "%s", err) continue } @@ -423,7 +426,7 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { if addressLabel != "" { s = fmt.Sprintf("%s [%s]", s, addressLabel) } - dbg.print(MachineInfo, s) + dbg.print(ui.MachineInfo, s) } case "RIOT": @@ -458,3 +461,8 @@ func (dbg *Debugger) parseCommand(input string) (bool, error) { return stepNext, nil } + +// GuessWord implements tabcompletion.TabCompleter interface +func (dbg *Debugger) GuessWord(input string) string { + return input +} diff --git a/debugger/machineinfo.go b/debugger/machineinfo.go index 7e11cb2a..c960e74a 100644 --- a/debugger/machineinfo.go +++ b/debugger/machineinfo.go @@ -1,5 +1,7 @@ package debugger +import "gopher2600/debugger/ui" + // types that satisfy machineInfo return information about the state of the // emulated machine. String() should return verbose info, while StringTerse() // the more terse equivalent. @@ -10,7 +12,7 @@ type machineInfo interface { } func (dbg Debugger) printMachineInfo(mi machineInfo) { - dbg.print(MachineInfo, "%s", dbg.sprintMachineInfo(mi)) + dbg.print(ui.MachineInfo, "%s", dbg.sprintMachineInfo(mi)) } func (dbg Debugger) sprintMachineInfo(mi machineInfo) string { diff --git a/debugger/print.go b/debugger/print.go index 10fe8161..8b8bcac1 100644 --- a/debugger/print.go +++ b/debugger/print.go @@ -1,38 +1,16 @@ package debugger import ( + "gopher2600/debugger/ui" "strings" ) -// PrintProfile specifies the printing mode -type PrintProfile int - -// enumeration of print profile types -const ( - // the following profiles ar generated as a result of the emulation - CPUStep PrintProfile = iota - VideoStep - MachineInfo - - // the following profiles are generated by the debugger in response to user - // input - Feedback - Prompt - Script - - // user input (not used by all user interface types [eg. echoing terminals]) - Input - - // errors can be generated by the emulation or the debugger - Error -) - // wrapper function for UserPrint(). useful for normalising the input string // before passing to the real UserPrint. it also allows us to easily obey // directives such as the silent directive without passing the burden onto UI // implementors -func (dbg Debugger) print(pp PrintProfile, s string, a ...interface{}) { - if dbg.uiSilent && pp != Error { +func (dbg Debugger) print(pp ui.PrintProfile, s string, a ...interface{}) { + if dbg.uiSilent && pp != ui.Error { return } diff --git a/debugger/script.go b/debugger/script.go index 3c206064..b265e0ea 100644 --- a/debugger/script.go +++ b/debugger/script.go @@ -2,6 +2,7 @@ package debugger import ( "fmt" + "gopher2600/debugger/ui" "os" "strings" ) @@ -62,14 +63,14 @@ func (dbg *Debugger) RunScript(scriptfile string, silent bool) error { for i := 0; i < len(lines); i++ { if strings.Trim(lines[i], " ") != "" { if !silent { - dbg.print(Script, lines[i]) + dbg.print(ui.Script, lines[i]) } next, err := dbg.parseInput(lines[i]) if err != nil { - dbg.print(Error, fmt.Sprintf("script error (%s): %s", scriptfile, err.Error())) + dbg.print(ui.Error, fmt.Sprintf("script error (%s): %s", scriptfile, err.Error())) } if next { - dbg.print(Error, fmt.Sprintf("script error (%s): use of '%s' is not recommended in scripts", scriptfile, lines[i])) + dbg.print(ui.Error, fmt.Sprintf("script error (%s): use of '%s' is not recommended in scripts", scriptfile, lines[i])) // make sure run state is still sane dbg.runUntilHalt = false diff --git a/debugger/traps.go b/debugger/traps.go index 9dc0c92e..c91cf791 100644 --- a/debugger/traps.go +++ b/debugger/traps.go @@ -2,6 +2,7 @@ package debugger import ( "fmt" + "gopher2600/debugger/ui" ) // traps keeps track of all the currently defined trappers @@ -37,7 +38,7 @@ func (tr *traps) check() bool { ntr := tr.traps[i].target.ToInt() != tr.traps[i].origValue if ntr { tr.traps[i].origValue = tr.traps[i].target.ToInt() - tr.dbg.print(Feedback, "trap on %s", tr.traps[i].target.ShortLabel()) + tr.dbg.print(ui.Feedback, "trap on %s", tr.traps[i].target.ShortLabel()) } trapped = ntr || trapped } @@ -47,7 +48,7 @@ func (tr *traps) check() bool { func (tr traps) list() { if len(tr.traps) == 0 { - tr.dbg.print(Feedback, "no traps") + tr.dbg.print(ui.Feedback, "no traps") } else { s := "" sep := "" @@ -55,7 +56,7 @@ func (tr traps) list() { s = fmt.Sprintf("%s%s%s", s, sep, tr.traps[i].target.ShortLabel()) sep = ", " } - tr.dbg.print(Feedback, s) + tr.dbg.print(ui.Feedback, s) } } @@ -70,7 +71,7 @@ func (tr *traps) parseTrap(parts []string) error { switch parts[i] { case "CLEAR": tr.clear() - tr.dbg.print(Feedback, "traps cleared") + tr.dbg.print(ui.Feedback, "traps cleared") return nil case "LIST": tr.list() diff --git a/debugger/ui.go b/debugger/ui/plainterminal.go similarity index 63% rename from debugger/ui.go rename to debugger/ui/plainterminal.go index 1a224d4e..70f01d0e 100644 --- a/debugger/ui.go +++ b/debugger/ui/plainterminal.go @@ -1,29 +1,10 @@ -package debugger +package ui import ( "fmt" "os" ) -// UserInterface defines the user interface operations required by the debugger -type UserInterface interface { - Initialise() error - CleanUp() - UserPrint(PrintProfile, string, ...interface{}) - UserRead([]byte, string) (int, error) -} - -// UserInterrupt can be returned by UserRead() when user has cause an -// interrupt (ie. CTRL-C) -type UserInterrupt struct { - Message string -} - -// implement Error interface for UserInterrupt -func (intr UserInterrupt) Error() string { - return intr.Message -} - // PlainTerminal is the default, most basic terminal interface type PlainTerminal struct { } @@ -37,6 +18,10 @@ func (pt *PlainTerminal) Initialise() error { func (pt *PlainTerminal) CleanUp() { } +// RegisterTabCompleter adds an implementation of TabCompleter to the terminal +func (pt *PlainTerminal) RegisterTabCompleter(tc TabCompleter) { +} + // UserPrint is the plain terminal print routine func (pt PlainTerminal) UserPrint(pp PrintProfile, s string, a ...interface{}) { switch pp { diff --git a/debugger/ui/printprofile.go b/debugger/ui/printprofile.go new file mode 100644 index 00000000..deded74e --- /dev/null +++ b/debugger/ui/printprofile.go @@ -0,0 +1,24 @@ +package ui + +// PrintProfile specifies the printing mode +type PrintProfile int + +// enumeration of print profile types +const ( + // the following profiles ar generated as a result of the emulation + CPUStep PrintProfile = iota + VideoStep + MachineInfo + + // the following profiles are generated by the debugger in response to user + // input + Feedback + Prompt + Script + + // user input (not used by all user interface types [eg. echoing terminals]) + Input + + // errors can be generated by the emulation or the debugger + Error +) diff --git a/debugger/ui/tabcompletion.go b/debugger/ui/tabcompletion.go new file mode 100644 index 00000000..5d9c8321 --- /dev/null +++ b/debugger/ui/tabcompletion.go @@ -0,0 +1,6 @@ +package ui + +// TabCompleter defines the types that can be used for tab completion +type TabCompleter interface { + GuessWord(string) string +} diff --git a/debugger/ui/ui.go b/debugger/ui/ui.go new file mode 100644 index 00000000..c69ed62e --- /dev/null +++ b/debugger/ui/ui.go @@ -0,0 +1,21 @@ +package ui + +// UserInterface defines the user interface operations required by the debugger +type UserInterface interface { + Initialise() error + CleanUp() + RegisterTabCompleter(TabCompleter) + UserPrint(PrintProfile, string, ...interface{}) + UserRead([]byte, string) (int, error) +} + +// UserInterrupt can be returned by UserRead() when user has cause an +// interrupt (ie. CTRL-C) +type UserInterrupt struct { + Message string +} + +// implement Error interface for UserInterrupt +func (intr UserInterrupt) Error() string { + return intr.Message +} diff --git a/gopher2600.go b/gopher2600.go index 03c92ff0..513e6dc2 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -5,6 +5,7 @@ import ( "fmt" "gopher2600/debugger" "gopher2600/debugger/colorterm" + "gopher2600/debugger/ui" "gopher2600/hardware" "gopher2600/television" "os" @@ -41,7 +42,7 @@ func main() { } // start debugger with choice of interface and cartridge - var term debugger.UserInterface + var term ui.UserInterface switch strings.ToUpper(*termType) { case "COLOR": @@ -50,7 +51,7 @@ func main() { fmt.Printf("! unknown terminal type (%s) defaulting to plain\n", *termType) fallthrough case "PLAIN": - term = new(debugger.PlainTerminal) + term = new(ui.PlainTerminal) } err = dbg.Start(term, cartridgeFile)