Console auto complete
This commit is contained in:
parent
ef203cf0e5
commit
c07588e3bd
@ -8,6 +8,7 @@
|
|||||||
#include "GBMemoryByteArray.h"
|
#include "GBMemoryByteArray.h"
|
||||||
#include "GBWarningPopover.h"
|
#include "GBWarningPopover.h"
|
||||||
#include "GBCheatWindowController.h"
|
#include "GBCheatWindowController.h"
|
||||||
|
#include "GBTerminalTextFieldCell.h"
|
||||||
|
|
||||||
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
|
/* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */
|
||||||
/* Todo: Split into category files! This is so messy!!! */
|
/* Todo: Split into category files! This is so messy!!! */
|
||||||
@ -546,6 +547,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||||||
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
|
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
|
||||||
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
|
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
|
||||||
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
|
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
|
||||||
|
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(updateSideView)
|
selector:@selector(updateSideView)
|
||||||
name:NSTextDidChangeNotification
|
name:NSTextDidChangeNotification
|
||||||
@ -1008,6 +1010,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||||||
[debugger_input_queue removeObjectAtIndex:0];
|
[debugger_input_queue removeObjectAtIndex:0];
|
||||||
}
|
}
|
||||||
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
|
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
|
||||||
|
if ((id)input == [NSNull null]) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
return input? strdup([input UTF8String]): NULL;
|
return input? strdup([input UTF8String]): NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#include <Core/gb.h>
|
||||||
|
|
||||||
@interface GBTerminalTextFieldCell : NSTextFieldCell
|
@interface GBTerminalTextFieldCell : NSTextFieldCell
|
||||||
|
@property GB_gameboy_t *gb;
|
||||||
@end
|
@end
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#import "GBTerminalTextFieldCell.h"
|
#import "GBTerminalTextFieldCell.h"
|
||||||
|
|
||||||
@interface GBTerminalTextView : NSTextView
|
@interface GBTerminalTextView : NSTextView
|
||||||
|
@property GB_gameboy_t *gb;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GBTerminalTextFieldCell
|
@implementation GBTerminalTextFieldCell
|
||||||
@ -12,10 +13,12 @@
|
|||||||
- (NSTextView *)fieldEditorForView:(NSView *)controlView
|
- (NSTextView *)fieldEditorForView:(NSView *)controlView
|
||||||
{
|
{
|
||||||
if (field_editor) {
|
if (field_editor) {
|
||||||
|
field_editor.gb = self.gb;
|
||||||
return field_editor;
|
return field_editor;
|
||||||
}
|
}
|
||||||
field_editor = [[GBTerminalTextView alloc] init];
|
field_editor = [[GBTerminalTextView alloc] init];
|
||||||
[field_editor setFieldEditor:YES];
|
[field_editor setFieldEditor:YES];
|
||||||
|
field_editor.gb = self.gb;
|
||||||
return field_editor;
|
return field_editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,6 +29,8 @@
|
|||||||
NSMutableOrderedSet *lines;
|
NSMutableOrderedSet *lines;
|
||||||
NSUInteger current_line;
|
NSUInteger current_line;
|
||||||
bool reverse_search_mode;
|
bool reverse_search_mode;
|
||||||
|
NSRange auto_complete_range;
|
||||||
|
uintptr_t auto_complete_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
@ -170,6 +175,7 @@
|
|||||||
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
|
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
|
||||||
{
|
{
|
||||||
reverse_search_mode = false;
|
reverse_search_mode = false;
|
||||||
|
auto_complete_context = 0;
|
||||||
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
|
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,6 +194,38 @@
|
|||||||
[attributes setObject:color forKey:NSForegroundColorAttributeName];
|
[attributes setObject:color forKey:NSForegroundColorAttributeName];
|
||||||
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
|
[[[NSAttributedString alloc] initWithString:@"Reverse search..." attributes:attributes] drawAtPoint:NSMakePoint(2, 0)];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Todo: lazy design, make it use a delegate instead of having a gb reference*/
|
||||||
|
|
||||||
|
- (void)insertTab:(id)sender
|
||||||
|
{
|
||||||
|
if (auto_complete_context == 0) {
|
||||||
|
NSRange selection = self.selectedRange;
|
||||||
|
if (selection.length) {
|
||||||
|
[self delete:nil];
|
||||||
|
}
|
||||||
|
auto_complete_range = NSMakeRange(selection.location, 0);
|
||||||
|
}
|
||||||
|
char *substring = strdup([self.string substringToIndex:auto_complete_range.location].UTF8String);
|
||||||
|
uintptr_t context = auto_complete_context;
|
||||||
|
char *completion = GB_debugger_complete_substring(self.gb, substring, &context);
|
||||||
|
free(substring);
|
||||||
|
if (completion) {
|
||||||
|
NSString *ns_completion = @(completion);
|
||||||
|
free(completion);
|
||||||
|
if (!ns_completion) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
self.selectedRange = auto_complete_range;
|
||||||
|
auto_complete_range.length = ns_completion.length;
|
||||||
|
[self replaceCharactersInRange:self.selectedRange withString:ns_completion];
|
||||||
|
auto_complete_context = context;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
error:
|
||||||
|
auto_complete_context = context;
|
||||||
|
NSBeep();
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
194
Core/debugger.c
194
Core/debugger.c
@ -689,6 +689,7 @@ exit:
|
|||||||
|
|
||||||
struct debugger_command_s;
|
struct debugger_command_s;
|
||||||
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command);
|
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command);
|
||||||
|
typedef char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context);
|
||||||
|
|
||||||
typedef struct debugger_command_s {
|
typedef struct debugger_command_s {
|
||||||
const char *command;
|
const char *command;
|
||||||
@ -697,6 +698,8 @@ typedef struct debugger_command_s {
|
|||||||
const char *help_string; // Null if should not appear in help
|
const char *help_string; // Null if should not appear in help
|
||||||
const char *arguments_format; // For usage message
|
const char *arguments_format; // For usage message
|
||||||
const char *modifiers_format; // For usage message
|
const char *modifiers_format; // For usage message
|
||||||
|
debugger_completer_imp_t *argument_completer;
|
||||||
|
debugger_completer_imp_t *modifiers_completer;
|
||||||
} debugger_command_t;
|
} debugger_command_t;
|
||||||
|
|
||||||
static const char *lstrip(const char *str)
|
static const char *lstrip(const char *str)
|
||||||
@ -832,6 +835,19 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *on_off_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||||
|
{
|
||||||
|
size_t length = strlen(string);
|
||||||
|
const char *suggestions[] = {"on", "off"};
|
||||||
|
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||||
|
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||||
|
return strdup(suggestions[(*context)++] + length);
|
||||||
|
}
|
||||||
|
(*context)++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Enable or disable software breakpoints */
|
/* Enable or disable software breakpoints */
|
||||||
static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||||
{
|
{
|
||||||
@ -873,6 +889,65 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
|
|||||||
return (uint16_t) min;
|
return (uint16_t) min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool is_legal_symbol_char(char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9') return true;
|
||||||
|
if (c >= 'A' && c <= 'Z') return true;
|
||||||
|
if (c >= 'a' && c <= 'z') return true;
|
||||||
|
if (c == '_') return true;
|
||||||
|
if (c == '.') return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_context)
|
||||||
|
{
|
||||||
|
const char *symbol_prefix = string;
|
||||||
|
while (*string) {
|
||||||
|
if (!is_legal_symbol_char(*string)) {
|
||||||
|
symbol_prefix = string + 1;
|
||||||
|
}
|
||||||
|
string++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*symbol_prefix == '$') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint16_t bank;
|
||||||
|
uint32_t symbol;
|
||||||
|
} *context = (void *)_context;
|
||||||
|
|
||||||
|
|
||||||
|
size_t length = strlen(symbol_prefix);
|
||||||
|
while (context->bank < 0x200) {
|
||||||
|
if (gb->bank_symbols[context->bank] == NULL ||
|
||||||
|
context->symbol >= gb->bank_symbols[context->bank]->n_symbols) {
|
||||||
|
context->bank++;
|
||||||
|
context->symbol = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name;
|
||||||
|
if (memcmp(symbol_prefix, candidate, length) == 0) {
|
||||||
|
return strdup(candidate + length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *j_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||||
|
{
|
||||||
|
size_t length = strlen(string);
|
||||||
|
const char *suggestions[] = {"j"};
|
||||||
|
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||||
|
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||||
|
return strdup(suggestions[(*context)++] + length);
|
||||||
|
}
|
||||||
|
(*context)++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||||
{
|
{
|
||||||
bool is_jump_to = true;
|
bool is_jump_to = true;
|
||||||
@ -1040,6 +1115,19 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
|
|||||||
return (uint16_t) min;
|
return (uint16_t) min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||||
|
{
|
||||||
|
size_t length = strlen(string);
|
||||||
|
const char *suggestions[] = {"r", "rw", "w"};
|
||||||
|
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||||
|
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||||
|
return strdup(suggestions[(*context)++] + length);
|
||||||
|
}
|
||||||
|
(*context)++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||||
{
|
{
|
||||||
if (strlen(lstrip(arguments)) == 0) {
|
if (strlen(lstrip(arguments)) == 0) {
|
||||||
@ -1277,6 +1365,19 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
|
|||||||
return _should_break(gb, full_addr, jump_to);
|
return _should_break(gb, full_addr, jump_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||||
|
{
|
||||||
|
size_t length = strlen(string);
|
||||||
|
const char *suggestions[] = {"a", "b", "d", "o", "x"};
|
||||||
|
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||||
|
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||||
|
return strdup(suggestions[(*context)++] + length);
|
||||||
|
}
|
||||||
|
(*context)++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||||
{
|
{
|
||||||
if (strlen(lstrip(arguments)) == 0) {
|
if (strlen(lstrip(arguments)) == 0) {
|
||||||
@ -1740,6 +1841,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *wave_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
|
||||||
|
{
|
||||||
|
size_t length = strlen(string);
|
||||||
|
const char *suggestions[] = {"c", "f", "l"};
|
||||||
|
while (*context < sizeof(suggestions) / sizeof(suggestions[0])) {
|
||||||
|
if (memcmp(string, suggestions[*context], length) == 0) {
|
||||||
|
return strdup(suggestions[(*context)++] + length);
|
||||||
|
}
|
||||||
|
(*context)++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||||
{
|
{
|
||||||
if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
|
if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
|
||||||
@ -1787,7 +1901,7 @@ static const debugger_command_t commands[] = {
|
|||||||
{"finish", 1, finish, "Run until the current function returns"},
|
{"finish", 1, finish, "Run until the current function returns"},
|
||||||
{"backtrace", 2, backtrace, "Displays the current call stack"},
|
{"backtrace", 2, backtrace, "Displays the current call stack"},
|
||||||
{"bt", 2, }, /* Alias */
|
{"bt", 2, }, /* Alias */
|
||||||
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"},
|
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
|
||||||
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
|
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
|
||||||
"used"},
|
"used"},
|
||||||
{"registers", 1, registers, "Print values of processor registers and other important registers"},
|
{"registers", 1, registers, "Print values of processor registers and other important registers"},
|
||||||
@ -1796,30 +1910,33 @@ static const debugger_command_t commands[] = {
|
|||||||
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
|
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
|
||||||
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
|
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
|
||||||
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
|
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
|
||||||
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"},
|
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
|
||||||
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
|
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
|
||||||
{"palettes", 3, palettes, "Displays the current CGB palettes"},
|
{"palettes", 3, palettes, "Displays the current CGB palettes"},
|
||||||
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)"},
|
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
|
||||||
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
|
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
|
||||||
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
|
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
|
||||||
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
|
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
|
||||||
"jumping to the target.",
|
"jumping to the target.",
|
||||||
"<expression>[ if <condition expression>]", "j"},
|
"<expression>[ if <condition expression>]", "j",
|
||||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"},
|
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
|
||||||
|
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||||
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
|
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
|
||||||
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
|
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
|
||||||
"Default watchpoint type is write-only.",
|
"Default watchpoint type is write-only.",
|
||||||
"<expression>[ if <condition expression>]", "(r|w|rw)"},
|
"<expression>[ if <condition expression>]", "(r|w|rw)",
|
||||||
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"},
|
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
|
||||||
|
},
|
||||||
|
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||||
{"list", 1, list, "List all set breakpoints and watchpoints"},
|
{"list", 1, list, "List all set breakpoints and watchpoints"},
|
||||||
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
|
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
|
||||||
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
|
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
|
||||||
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
|
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
|
||||||
"<expression>", "format"},
|
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
|
||||||
{"eval", 2, }, /* Alias */
|
{"eval", 2, }, /* Alias */
|
||||||
{"examine", 2, examine, "Examine values at address", "<expression>", "count"},
|
{"examine", 2, examine, "Examine values at address", "<expression>", "count", .argument_completer = symbol_completer},
|
||||||
{"x", 1, }, /* Alias */
|
{"x", 1, }, /* Alias */
|
||||||
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count"},
|
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count", .argument_completer = symbol_completer},
|
||||||
|
|
||||||
|
|
||||||
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
|
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
|
||||||
@ -2075,6 +2192,63 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns true if debugger waits for more commands */
|
||||||
|
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context)
|
||||||
|
{
|
||||||
|
char *command_string = input;
|
||||||
|
char *arguments = strchr(input, ' ');
|
||||||
|
if (arguments) {
|
||||||
|
/* Actually "split" the string. */
|
||||||
|
arguments[0] = 0;
|
||||||
|
arguments++;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *modifiers = strchr(command_string, '/');
|
||||||
|
if (modifiers) {
|
||||||
|
/* Actually "split" the string. */
|
||||||
|
modifiers[0] = 0;
|
||||||
|
modifiers++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugger_command_t *command = find_command(command_string);
|
||||||
|
if (command && command->implementation == help && arguments) {
|
||||||
|
command_string = arguments;
|
||||||
|
arguments = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No commands and no modifiers, complete the command */
|
||||||
|
if (!arguments && !modifiers) {
|
||||||
|
size_t length = strlen(command_string);
|
||||||
|
if (*context >= sizeof(commands) / sizeof(commands[0])) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (const debugger_command_t *command = &commands[*context]; command->command; command++) {
|
||||||
|
(*context)++;
|
||||||
|
if (memcmp(command->command, command_string, length) == 0) { /* Is a substring? */
|
||||||
|
return strdup(command->command + length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
if (arguments) {
|
||||||
|
if (command->argument_completer) {
|
||||||
|
return command->argument_completer(gb, arguments, context);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiers) {
|
||||||
|
if (command->modifiers_completer) {
|
||||||
|
return command->modifiers_completer(gb, modifiers, context);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JUMP_TO_NONE,
|
JUMP_TO_NONE,
|
||||||
JUMP_TO_BREAK,
|
JUMP_TO_BREAK,
|
||||||
|
@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
|
|||||||
void
|
void
|
||||||
#endif
|
#endif
|
||||||
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
|
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
|
||||||
|
char *GB_debugger_complete_substring(GB_gameboy_t *gb, char *input, uintptr_t *context); /* Destroys input, result requires free */
|
||||||
|
|
||||||
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
|
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
|
||||||
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);
|
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);
|
||||||
|
Loading…
Reference in New Issue
Block a user