mirror of https://github.com/stella-emu/stella.git
Tab completion in the PromptWidget is now working again. As well,
the completion now includes functions (built-in and user-defined). Cleaned up some more of the debugger documentation, mostly related to tab completion. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@1992 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
parent
72b2ddd89b
commit
964415508d
|
@ -53,7 +53,7 @@ feature that no other 2600 debugger has; it's <b>completely</b> cross-platform.<
|
|||
<li><strike>Symbolic names in disassembly.</strike> <b>Note:</b> Disabled until a future release</li>
|
||||
|
||||
<li>Symbolic names accepted as input.</li>
|
||||
<li>Tab completion for commands and symbol names.</li>
|
||||
<li>Tab completion for commands, symbol names and functions.</li>
|
||||
<li>Graphical editor for RIOT and extended RAM. Acts a lot like a spreadsheet.
|
||||
Input in hex, with displays for label/decimal/binary for
|
||||
currently-selected location.</li>
|
||||
|
@ -186,7 +186,7 @@ Bash-style commands are also supported:</p>
|
|||
<p>You can also scroll with the mouse. Copy and paste is not yet supported.</p>
|
||||
|
||||
<p>To see the available commands, enter "help". Bash-style tab completion is
|
||||
supported for commands and labels (see below).</p>
|
||||
supported for commands, labels and functions (see below).</p>
|
||||
|
||||
<p>For now, there are some functions that only exist in the prompt. We
|
||||
intend to add GUI equivalents for all (or almost all?) of the prompt
|
||||
|
@ -196,7 +196,7 @@ debugger without typing (or without typing much, anyway).</p>
|
|||
|
||||
<h4>Tab completion</h4>
|
||||
|
||||
<p>While entering a command or label, you can type a partial name and
|
||||
<p>While entering a command, label or function, you can type a partial name and
|
||||
press the Tab key to attempt to auto-complete it. If you've ever used
|
||||
"bash", this will be immediately familiar. If not, try it: load up
|
||||
a ROM, go to the debugger, type "print w" (but don't press Enter),
|
||||
|
@ -206,9 +206,9 @@ completions (try with "v" instead of "w"), you'll see a list of them,
|
|||
and your partial name will be completed as far as possible.</p>
|
||||
|
||||
<p>Tab completion works on all labels: built-in, loaded from a symbol file,
|
||||
or set during debugging with the "define" command. However, it does not
|
||||
yet work on functions defined with the "function" command, nor does it
|
||||
work on filenames.</p>
|
||||
or set during debugging with the "define" command. It also works with
|
||||
built-in functions and those defined with the "function" command,
|
||||
but it doesn't (yet) work on filenames.</p>
|
||||
|
||||
<h4>Expressions</h4>
|
||||
|
||||
|
|
|
@ -463,48 +463,27 @@ string CartDebug::loadSymbolFile(const string& f)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
int CartDebug::countCompletions(const char *in)
|
||||
void CartDebug::getCompletions(const char* in, StringList& completions) const
|
||||
{
|
||||
/* FIXME
|
||||
myCompletions = myCompPrefix = "";
|
||||
return countCompletions(in, mySystemAddresses) +
|
||||
countCompletions(in, myUserAddresses);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
// First scan system equates
|
||||
for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
|
||||
if(BSPF_strncasecmp(ourTIAMnemonicR[addr], in, strlen(in)) == 0)
|
||||
completions.push_back(ourTIAMnemonicR[addr]);
|
||||
for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
|
||||
if(BSPF_strncasecmp(ourTIAMnemonicW[addr], in, strlen(in)) == 0)
|
||||
completions.push_back(ourTIAMnemonicW[addr]);
|
||||
for(uInt16 addr = 0; addr <= 0x297-0x280; ++addr)
|
||||
if(BSPF_strncasecmp(ourIOMnemonic[addr], in, strlen(in)) == 0)
|
||||
completions.push_back(ourIOMnemonic[addr]);
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
int CartDebug::countCompletions(const char *in, LabelToAddr& addresses)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
LabelToAddr::iterator iter;
|
||||
for(iter = addresses.begin(); iter != addresses.end(); iter++)
|
||||
// Now scan user-defined labels
|
||||
LabelToAddr::const_iterator iter;
|
||||
for(iter = myUserAddresses.begin(); iter != myUserAddresses.end(); ++iter)
|
||||
{
|
||||
const char *l = iter->first.c_str();
|
||||
|
||||
const char* l = iter->first.c_str();
|
||||
if(BSPF_strncasecmp(l, in, strlen(in)) == 0)
|
||||
{
|
||||
if(myCompPrefix == "")
|
||||
myCompPrefix += l;
|
||||
else
|
||||
{
|
||||
int nonMatch = 0;
|
||||
const char *c = myCompPrefix.c_str();
|
||||
while(*c != '\0' && tolower(*c) == tolower(l[nonMatch]))
|
||||
{
|
||||
c++;
|
||||
nonMatch++;
|
||||
}
|
||||
myCompPrefix.erase(nonMatch, myCompPrefix.length());
|
||||
}
|
||||
|
||||
if(count++) myCompletions += " ";
|
||||
myCompletions += l;
|
||||
}
|
||||
completions.push_back(l);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -26,6 +26,7 @@ class System;
|
|||
#include "bspf.hxx"
|
||||
#include "Array.hxx"
|
||||
#include "Cart.hxx"
|
||||
#include "StringList.hxx"
|
||||
#include "DebuggerSystem.hxx"
|
||||
|
||||
// pointer types for CartDebug instance methods
|
||||
|
@ -150,7 +151,6 @@ class CartDebug : public DebuggerSystem
|
|||
*/
|
||||
string getCartType();
|
||||
|
||||
////////////////////////////////////////
|
||||
/**
|
||||
Add a label and associated address.
|
||||
Labels that reference either TIA or RIOT spaces will not be processed.
|
||||
|
@ -183,11 +183,9 @@ class CartDebug : public DebuggerSystem
|
|||
|
||||
/**
|
||||
Methods used by the command parser for tab-completion
|
||||
In this case, return completions from the equate list(s)
|
||||
*/
|
||||
int countCompletions(const char *in);
|
||||
const string& getCompletions() const { return myCompletions; }
|
||||
const string& getCompletionPrefix() const { return myCompPrefix; }
|
||||
////////////////////////////////////////
|
||||
void getCompletions(const char* in, StringList& list) const;
|
||||
|
||||
private:
|
||||
typedef map<uInt16, string> AddrToLabel;
|
||||
|
@ -210,9 +208,6 @@ class CartDebug : public DebuggerSystem
|
|||
string extractLabel(char *c) const;
|
||||
int extractValue(char *c) const;
|
||||
|
||||
// Count completions for the given mapping
|
||||
int countCompletions(const char *in, LabelToAddr& addresses);
|
||||
|
||||
private:
|
||||
CartState myState;
|
||||
CartState myOldState;
|
||||
|
@ -242,9 +237,8 @@ class CartDebug : public DebuggerSystem
|
|||
// handled differently
|
||||
LabelToAddr mySystemAddresses;
|
||||
|
||||
string myCompletions;
|
||||
string myCompPrefix;
|
||||
|
||||
// Holds address at which the most recent read from a write port
|
||||
// occurred
|
||||
uInt16 myRWPortAddress;
|
||||
|
||||
/// Table of instruction mnemonics
|
||||
|
|
|
@ -234,86 +234,6 @@ void Debugger::quit()
|
|||
myOSystem->eventHandler().leaveDebugMode();
|
||||
}
|
||||
|
||||
#if 0 // FIXME - remove this
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
string Debugger::loadListFile(string f)
|
||||
{
|
||||
char buffer[255];
|
||||
|
||||
if(f == "")
|
||||
{
|
||||
f = myOSystem->romFile();
|
||||
|
||||
string::size_type pos;
|
||||
if( (pos = f.find_last_of('.')) != string::npos )
|
||||
f.replace(pos, f.size(), ".lst");
|
||||
else
|
||||
f += ".lst";
|
||||
}
|
||||
|
||||
ifstream in(f.c_str());
|
||||
if(!in.is_open())
|
||||
return "Unable to read listing from " + f;
|
||||
|
||||
sourceLines.clear();
|
||||
int count = 0;
|
||||
while( !in.eof() )
|
||||
{
|
||||
if(!in.getline(buffer, 255))
|
||||
break;
|
||||
|
||||
if(strlen(buffer) >= 14 &&
|
||||
buffer[0] == ' ' &&
|
||||
buffer[7] == ' ' &&
|
||||
buffer[8] == ' ' &&
|
||||
isxdigit(buffer[9]) &&
|
||||
isxdigit(buffer[12]) &&
|
||||
BSPF_isblank(buffer[13]))
|
||||
{
|
||||
count++;
|
||||
char addr[5];
|
||||
for(int i=0; i<4; i++)
|
||||
addr[i] = buffer[9+i];
|
||||
|
||||
for(char *c = buffer; *c != '\0'; c++)
|
||||
if(*c == '\t') *c = ' ';
|
||||
|
||||
addr[4] = '\0';
|
||||
string a = addr;
|
||||
string b = buffer;
|
||||
sourceLines.insert(make_pair(a, b));
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
|
||||
return valueToString(count) + " lines loaded from " + f;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
string Debugger::getSourceLines(int addr) const
|
||||
{
|
||||
if(sourceLines.size() == 0)
|
||||
return "";
|
||||
|
||||
string ret;
|
||||
string want = to_hex_16(addr);
|
||||
|
||||
bool found = false;
|
||||
pair<ListIter, ListIter> lines = sourceLines.equal_range(want);
|
||||
for(ListIter i = lines.first; i != lines.second; i++)
|
||||
{
|
||||
found = true;
|
||||
ret += i->second;
|
||||
ret += "\n";
|
||||
}
|
||||
|
||||
if(found)
|
||||
return ret;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Debugger::autoExec()
|
||||
{
|
||||
|
@ -831,6 +751,28 @@ const string Debugger::builtinHelp() const
|
|||
return buf.str();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Debugger::getCompletions(const char* in, StringList& list) const
|
||||
{
|
||||
// First check built-in functions
|
||||
FunctionMap::const_iterator iter1;
|
||||
for(iter1 = functions.begin(); iter1 != functions.end(); ++iter1)
|
||||
{
|
||||
const char* l = iter1->first.c_str();
|
||||
if(BSPF_strncasecmp(l, in, strlen(in)) == 0)
|
||||
list.push_back(l);
|
||||
}
|
||||
|
||||
// Now consider user-defined functions
|
||||
FunctionDefMap::const_iterator iter2;
|
||||
for(iter2 = functionDefs.begin(); iter2 != functionDefs.end(); ++iter2)
|
||||
{
|
||||
const char* l = iter2->first.c_str();
|
||||
if(BSPF_strncasecmp(l, in, strlen(in)) == 0)
|
||||
list.push_back(l);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Debugger::saveROM(const string& filename) const
|
||||
{
|
||||
|
|
|
@ -129,6 +129,12 @@ class Debugger : public DialogContainer
|
|||
const FunctionDefMap getFunctionDefMap() const;
|
||||
const string builtinHelp() const;
|
||||
|
||||
/**
|
||||
Methods used by the command parser for tab-completion
|
||||
In this case, return completions from the function list
|
||||
*/
|
||||
void getCompletions(const char* in, StringList& list) const;
|
||||
|
||||
/**
|
||||
The debugger subsystem responsible for all CPU state
|
||||
*/
|
||||
|
|
|
@ -157,50 +157,17 @@ string DebuggerParser::exec(const string& file, bool verbose)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// completion-related stuff:
|
||||
int DebuggerParser::countCompletions(const char *in)
|
||||
// Completion-related stuff:
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void DebuggerParser::getCompletions(const char* in, StringList& completions) const
|
||||
{
|
||||
int count = 0;
|
||||
completions = compPrefix = "";
|
||||
|
||||
// cerr << "Attempting to complete \"" << in << "\"" << endl;
|
||||
for(int i = 0; i < kNumCommands; ++i)
|
||||
{
|
||||
const char *l = commands[i].cmdString.c_str();
|
||||
|
||||
if(BSPF_strncasecmp(l, in, strlen(in)) == 0) {
|
||||
if(compPrefix == "")
|
||||
compPrefix += l;
|
||||
else {
|
||||
int nonMatch = 0;
|
||||
const char *c = compPrefix.c_str();
|
||||
while(*c != '\0' && tolower(*c) == tolower(l[nonMatch])) {
|
||||
c++;
|
||||
nonMatch++;
|
||||
}
|
||||
compPrefix.erase(nonMatch, compPrefix.length());
|
||||
// cerr << "compPrefix==" << compPrefix << endl;
|
||||
}
|
||||
|
||||
if(count++) completions += " ";
|
||||
completions += l;
|
||||
}
|
||||
const char* l = commands[i].cmdString.c_str();
|
||||
if(BSPF_strncasecmp(l, in, strlen(in)) == 0)
|
||||
completions.push_back(l);
|
||||
}
|
||||
|
||||
// cerr << "Found " << count << " label(s):" << endl << completions << endl;
|
||||
return count;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
const char *DebuggerParser::getCompletions()
|
||||
{
|
||||
return completions.c_str();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
const char *DebuggerParser::getCompletionPrefix()
|
||||
{
|
||||
return compPrefix.c_str();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -48,9 +48,7 @@ class DebuggerParser
|
|||
|
||||
/** Given a substring, determine matching substrings from the list
|
||||
of available commands. Used in the debugger prompt for tab-completion */
|
||||
int countCompletions(const char *in);
|
||||
const char *getCompletions();
|
||||
const char *getCompletionPrefix();
|
||||
void getCompletions(const char* in, StringList& list) const;
|
||||
|
||||
/** Evaluate the given expression using operators, current base, etc */
|
||||
int decipher_arg(const string &str);
|
||||
|
@ -131,10 +129,6 @@ class DebuggerParser
|
|||
BaseFormat defaultBase;
|
||||
StringList watches;
|
||||
|
||||
// Used in 'tab-completion', holds list of commands related to a substring
|
||||
string completions;
|
||||
string compPrefix;
|
||||
|
||||
// List of available command methods
|
||||
void executeA();
|
||||
void executeBank();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "Debugger.hxx"
|
||||
#include "DebuggerDialog.hxx"
|
||||
#include "DebuggerParser.hxx"
|
||||
#include "StringList.hxx"
|
||||
|
||||
#include "PromptWidget.hxx"
|
||||
#include "CartDebug.hxx"
|
||||
|
@ -220,7 +221,7 @@ bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
|
|||
int lastDelimPos = -1;
|
||||
char delimiter = '\0';
|
||||
|
||||
char *str = new char[len + 1];
|
||||
char str[len + 1];
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
str[i] = buffer(_promptStartPos + i) & 0x7f;
|
||||
|
@ -232,46 +233,50 @@ bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
|
|||
}
|
||||
str[len] = '\0';
|
||||
|
||||
const char *completionList;
|
||||
const char *prefix;
|
||||
int possibilities;
|
||||
StringList list;
|
||||
string completionList;
|
||||
string prefix;
|
||||
|
||||
if(lastDelimPos < 0)
|
||||
{
|
||||
// no delimiters, do command completion:
|
||||
DebuggerParser& parser = instance().debugger().parser();
|
||||
possibilities = parser.countCompletions(str);
|
||||
const DebuggerParser& parser = instance().debugger().parser();
|
||||
parser.getCompletions(str, list);
|
||||
|
||||
if(possibilities < 1) {
|
||||
delete[] str;
|
||||
if(list.size() < 1)
|
||||
break;
|
||||
}
|
||||
|
||||
completionList = parser.getCompletions();
|
||||
prefix = parser.getCompletionPrefix();
|
||||
// TODO: sort completions (add sort method to StringList)
|
||||
completionList = list[0];
|
||||
for(uInt32 i = 1; i < list.size(); ++i)
|
||||
completionList += " " + list[i];
|
||||
prefix = getCompletionPrefix(list, str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we got a delimiter, so this must be a label:
|
||||
CartDebug& cart = instance().debugger().cartDebug();
|
||||
possibilities = cart.countCompletions(str + lastDelimPos + 1);
|
||||
// we got a delimiter, so this must be a label or a function
|
||||
const Debugger& dbg = instance().debugger();
|
||||
|
||||
if(possibilities < 1) {
|
||||
delete[] str;
|
||||
dbg.cartDebug().getCompletions(str + lastDelimPos + 1, list);
|
||||
dbg.getCompletions(str + lastDelimPos + 1, list);
|
||||
|
||||
if(list.size() < 1)
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - perhaps use strings instead of char pointers
|
||||
completionList = cart.getCompletions().c_str();
|
||||
prefix = cart.getCompletionPrefix().c_str();
|
||||
// TODO: sort completions (add sort method to StringList)
|
||||
completionList = list[0];
|
||||
for(uInt32 i = 1; i < list.size(); ++i)
|
||||
completionList += " " + list[i];
|
||||
prefix = getCompletionPrefix(list, str + lastDelimPos + 1);
|
||||
}
|
||||
|
||||
if(possibilities == 1)
|
||||
if(list.size() == 1)
|
||||
{
|
||||
// add to buffer as though user typed it (plus a space)
|
||||
_currentPos = _promptStartPos + lastDelimPos + 1;
|
||||
while(*completionList != '\0')
|
||||
putcharIntern(*completionList++);
|
||||
const char* clptr = completionList.c_str();
|
||||
while(*clptr != '\0')
|
||||
putcharIntern(*clptr++);
|
||||
|
||||
putcharIntern(' ');
|
||||
_promptEndPos = _currentPos;
|
||||
|
@ -298,7 +303,6 @@ bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
|
|||
print(prefix);
|
||||
_promptEndPos = _currentPos;
|
||||
}
|
||||
delete[] str;
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
|
@ -883,3 +887,30 @@ bool PromptWidget::saveBuffer(string& filename)
|
|||
out.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
string PromptWidget::getCompletionPrefix(const StringList& completions, string prefix)
|
||||
{
|
||||
// Search for prefix in every string, progressively growing it
|
||||
// Once a mismatch is found or length is past one of the strings, we're done
|
||||
// We *could* use the longest common string algorithm, but for the lengths
|
||||
// of the strings we're dealing with, it's probably not worth it
|
||||
for(;;)
|
||||
{
|
||||
for(uInt32 i = 0; i < completions.size(); ++i)
|
||||
{
|
||||
const string& s = completions[i];
|
||||
if(s.length() < prefix.length())
|
||||
return prefix; // current prefix is the best we're going to get
|
||||
else if(s.compare(0, prefix.length(), prefix) != 0)
|
||||
{
|
||||
prefix.erase(prefix.length()-1);
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
if(completions[0].length() > prefix.length())
|
||||
prefix = completions[0].substr(0, prefix.length() + 1);
|
||||
else
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,10 @@ class PromptWidget : public Widget, public CommandSender
|
|||
|
||||
void loadConfig();
|
||||
|
||||
private:
|
||||
// Get the longest prefix (initially 's') that is in every string in the list
|
||||
string getCompletionPrefix(const StringList& completions, string s);
|
||||
|
||||
private:
|
||||
enum {
|
||||
kBufferSize = 32768,
|
||||
|
|
Loading…
Reference in New Issue