mirror of https://github.com/bsnes-emu/bsnes.git
Console auto complete
This commit is contained in:
parent
ef203cf0e5
commit
c07588e3bd
|
@ -8,6 +8,7 @@
|
|||
#include "GBMemoryByteArray.h"
|
||||
#include "GBWarningPopover.h"
|
||||
#include "GBCheatWindowController.h"
|
||||
#include "GBTerminalTextFieldCell.h"
|
||||
|
||||
/* 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!!! */
|
||||
|
@ -546,6 +547,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
self.debuggerSideViewInput.textColor = [NSColor whiteColor];
|
||||
self.debuggerSideViewInput.defaultParagraphStyle = paragraph_style;
|
||||
[self.debuggerSideViewInput setString:@"registers\nbacktrace\n"];
|
||||
((GBTerminalTextFieldCell *)self.consoleInput.cell).gb = &gb;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(updateSideView)
|
||||
name:NSTextDidChangeNotification
|
||||
|
@ -1008,6 +1010,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
|||
[debugger_input_queue removeObjectAtIndex:0];
|
||||
}
|
||||
[has_debugger_input unlockWithCondition:[debugger_input_queue count] != 0];
|
||||
if ((id)input == [NSNull null]) {
|
||||
return NULL;
|
||||
}
|
||||
return input? strdup([input UTF8String]): NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
|
||||
@interface GBTerminalTextFieldCell : NSTextFieldCell
|
||||
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#import "GBTerminalTextFieldCell.h"
|
||||
|
||||
@interface GBTerminalTextView : NSTextView
|
||||
@property GB_gameboy_t *gb;
|
||||
@end
|
||||
|
||||
@implementation GBTerminalTextFieldCell
|
||||
|
@ -12,10 +13,12 @@
|
|||
- (NSTextView *)fieldEditorForView:(NSView *)controlView
|
||||
{
|
||||
if (field_editor) {
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
}
|
||||
field_editor = [[GBTerminalTextView alloc] init];
|
||||
[field_editor setFieldEditor:YES];
|
||||
field_editor.gb = self.gb;
|
||||
return field_editor;
|
||||
}
|
||||
|
||||
|
@ -26,6 +29,8 @@
|
|||
NSMutableOrderedSet *lines;
|
||||
NSUInteger current_line;
|
||||
bool reverse_search_mode;
|
||||
NSRange auto_complete_range;
|
||||
uintptr_t auto_complete_context;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
|
@ -170,6 +175,7 @@
|
|||
-(void)setSelectedRanges:(NSArray<NSValue *> *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
|
||||
{
|
||||
reverse_search_mode = false;
|
||||
auto_complete_context = 0;
|
||||
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
|
||||
}
|
||||
|
||||
|
@ -188,6 +194,38 @@
|
|||
[attributes setObject:color forKey:NSForegroundColorAttributeName];
|
||||
[[[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
|
||||
|
|
194
Core/debugger.c
194
Core/debugger.c
|
@ -689,6 +689,7 @@ exit:
|
|||
|
||||
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 char *debugger_completer_imp_t(GB_gameboy_t *gb, const char *string, uintptr_t *context);
|
||||
|
||||
typedef struct debugger_command_s {
|
||||
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 *arguments_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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (strlen(lstrip(arguments)) == 0) {
|
||||
|
@ -1740,6 +1841,19 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||
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)
|
||||
{
|
||||
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"},
|
||||
{"backtrace", 2, backtrace, "Displays the current call stack"},
|
||||
{"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
|
||||
"used"},
|
||||
{"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"},
|
||||
{"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
|
||||
"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"},
|
||||
{"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
|
||||
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
|
||||
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
|
||||
"jumping to the target.",
|
||||
"<expression>[ if <condition expression>]", "j"},
|
||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"},
|
||||
"<expression>[ if <condition expression>]", "j",
|
||||
.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
|
||||
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
|
||||
"Default watchpoint type is write-only.",
|
||||
"<expression>[ if <condition expression>]", "(r|w|rw)"},
|
||||
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"},
|
||||
"<expression>[ if <condition expression>]", "(r|w|rw)",
|
||||
.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"},
|
||||
{"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
|
||||
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
|
||||
"<expression>", "format"},
|
||||
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
|
||||
{"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 */
|
||||
{"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>]"},
|
||||
|
@ -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 {
|
||||
JUMP_TO_NONE,
|
||||
JUMP_TO_BREAK,
|
||||
|
|
|
@ -34,7 +34,7 @@ bool /* Returns true if debugger waits for more commands. Not relevant for non-G
|
|||
void
|
||||
#endif
|
||||
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);
|
||||
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);
|
||||
|
|
Loading…
Reference in New Issue