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:
stephena 2010-04-08 18:21:00 +00:00
parent 72b2ddd89b
commit 964415508d
9 changed files with 121 additions and 204 deletions

View File

@ -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><strike>Symbolic names in disassembly.</strike> <b>Note:</b> Disabled until a future release</li>
<li>Symbolic names accepted as input.</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. <li>Graphical editor for RIOT and extended RAM. Acts a lot like a spreadsheet.
Input in hex, with displays for label/decimal/binary for Input in hex, with displays for label/decimal/binary for
currently-selected location.</li> 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>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 <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 <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 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> <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 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 "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), 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> 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, <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 or set during debugging with the "define" command. It also works with
yet work on functions defined with the "function" command, nor does it built-in functions and those defined with the "function" command,
work on filenames.</p> but it doesn't (yet) work on filenames.</p>
<h4>Expressions</h4> <h4>Expressions</h4>

View File

@ -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 // First scan system equates
myCompletions = myCompPrefix = ""; for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
return countCompletions(in, mySystemAddresses) + if(BSPF_strncasecmp(ourTIAMnemonicR[addr], in, strlen(in)) == 0)
countCompletions(in, myUserAddresses); completions.push_back(ourTIAMnemonicR[addr]);
*/ for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
return 0; 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]);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Now scan user-defined labels
int CartDebug::countCompletions(const char *in, LabelToAddr& addresses) LabelToAddr::const_iterator iter;
{ for(iter = myUserAddresses.begin(); iter != myUserAddresses.end(); ++iter)
int count = 0;
LabelToAddr::iterator iter;
for(iter = addresses.begin(); iter != addresses.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(BSPF_strncasecmp(l, in, strlen(in)) == 0)
{ completions.push_back(l);
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;
}
} }
return count;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -26,6 +26,7 @@ class System;
#include "bspf.hxx" #include "bspf.hxx"
#include "Array.hxx" #include "Array.hxx"
#include "Cart.hxx" #include "Cart.hxx"
#include "StringList.hxx"
#include "DebuggerSystem.hxx" #include "DebuggerSystem.hxx"
// pointer types for CartDebug instance methods // pointer types for CartDebug instance methods
@ -150,7 +151,6 @@ class CartDebug : public DebuggerSystem
*/ */
string getCartType(); string getCartType();
////////////////////////////////////////
/** /**
Add a label and associated address. Add a label and associated address.
Labels that reference either TIA or RIOT spaces will not be processed. 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 Methods used by the command parser for tab-completion
In this case, return completions from the equate list(s)
*/ */
int countCompletions(const char *in); void getCompletions(const char* in, StringList& list) const;
const string& getCompletions() const { return myCompletions; }
const string& getCompletionPrefix() const { return myCompPrefix; }
////////////////////////////////////////
private: private:
typedef map<uInt16, string> AddrToLabel; typedef map<uInt16, string> AddrToLabel;
@ -210,9 +208,6 @@ class CartDebug : public DebuggerSystem
string extractLabel(char *c) const; string extractLabel(char *c) const;
int extractValue(char *c) const; int extractValue(char *c) const;
// Count completions for the given mapping
int countCompletions(const char *in, LabelToAddr& addresses);
private: private:
CartState myState; CartState myState;
CartState myOldState; CartState myOldState;
@ -242,9 +237,8 @@ class CartDebug : public DebuggerSystem
// handled differently // handled differently
LabelToAddr mySystemAddresses; LabelToAddr mySystemAddresses;
string myCompletions; // Holds address at which the most recent read from a write port
string myCompPrefix; // occurred
uInt16 myRWPortAddress; uInt16 myRWPortAddress;
/// Table of instruction mnemonics /// Table of instruction mnemonics

View File

@ -234,86 +234,6 @@ void Debugger::quit()
myOSystem->eventHandler().leaveDebugMode(); 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() void Debugger::autoExec()
{ {
@ -831,6 +751,28 @@ const string Debugger::builtinHelp() const
return buf.str(); 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 bool Debugger::saveROM(const string& filename) const
{ {

View File

@ -129,6 +129,12 @@ class Debugger : public DialogContainer
const FunctionDefMap getFunctionDefMap() const; const FunctionDefMap getFunctionDefMap() const;
const string builtinHelp() 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 The debugger subsystem responsible for all CPU state
*/ */

View File

@ -157,50 +157,17 @@ string DebuggerParser::exec(const string& file, bool verbose)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// completion-related stuff: // Completion-related stuff:
int DebuggerParser::countCompletions(const char *in) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DebuggerParser::getCompletions(const char* in, StringList& completions) const
{ {
int count = 0;
completions = compPrefix = "";
// cerr << "Attempting to complete \"" << in << "\"" << endl; // cerr << "Attempting to complete \"" << in << "\"" << endl;
for(int i = 0; i < kNumCommands; ++i) for(int i = 0; i < kNumCommands; ++i)
{ {
const char *l = commands[i].cmdString.c_str(); const char* l = commands[i].cmdString.c_str();
if(BSPF_strncasecmp(l, in, strlen(in)) == 0)
if(BSPF_strncasecmp(l, in, strlen(in)) == 0) { completions.push_back(l);
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;
}
} }
// 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();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -48,9 +48,7 @@ class DebuggerParser
/** Given a substring, determine matching substrings from the list /** Given a substring, determine matching substrings from the list
of available commands. Used in the debugger prompt for tab-completion */ of available commands. Used in the debugger prompt for tab-completion */
int countCompletions(const char *in); void getCompletions(const char* in, StringList& list) const;
const char *getCompletions();
const char *getCompletionPrefix();
/** Evaluate the given expression using operators, current base, etc */ /** Evaluate the given expression using operators, current base, etc */
int decipher_arg(const string &str); int decipher_arg(const string &str);
@ -131,10 +129,6 @@ class DebuggerParser
BaseFormat defaultBase; BaseFormat defaultBase;
StringList watches; StringList watches;
// Used in 'tab-completion', holds list of commands related to a substring
string completions;
string compPrefix;
// List of available command methods // List of available command methods
void executeA(); void executeA();
void executeBank(); void executeBank();

View File

@ -29,6 +29,7 @@
#include "Debugger.hxx" #include "Debugger.hxx"
#include "DebuggerDialog.hxx" #include "DebuggerDialog.hxx"
#include "DebuggerParser.hxx" #include "DebuggerParser.hxx"
#include "StringList.hxx"
#include "PromptWidget.hxx" #include "PromptWidget.hxx"
#include "CartDebug.hxx" #include "CartDebug.hxx"
@ -220,7 +221,7 @@ bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
int lastDelimPos = -1; int lastDelimPos = -1;
char delimiter = '\0'; char delimiter = '\0';
char *str = new char[len + 1]; char str[len + 1];
for (i = 0; i < len; i++) for (i = 0; i < len; i++)
{ {
str[i] = buffer(_promptStartPos + i) & 0x7f; str[i] = buffer(_promptStartPos + i) & 0x7f;
@ -232,46 +233,50 @@ bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
} }
str[len] = '\0'; str[len] = '\0';
const char *completionList; StringList list;
const char *prefix; string completionList;
int possibilities; string prefix;
if(lastDelimPos < 0) if(lastDelimPos < 0)
{ {
// no delimiters, do command completion: // no delimiters, do command completion:
DebuggerParser& parser = instance().debugger().parser(); const DebuggerParser& parser = instance().debugger().parser();
possibilities = parser.countCompletions(str); parser.getCompletions(str, list);
if(possibilities < 1) { if(list.size() < 1)
delete[] str;
break; break;
}
completionList = parser.getCompletions(); // TODO: sort completions (add sort method to StringList)
prefix = parser.getCompletionPrefix(); completionList = list[0];
for(uInt32 i = 1; i < list.size(); ++i)
completionList += " " + list[i];
prefix = getCompletionPrefix(list, str);
} }
else else
{ {
// we got a delimiter, so this must be a label: // we got a delimiter, so this must be a label or a function
CartDebug& cart = instance().debugger().cartDebug(); const Debugger& dbg = instance().debugger();
possibilities = cart.countCompletions(str + lastDelimPos + 1);
if(possibilities < 1) { dbg.cartDebug().getCompletions(str + lastDelimPos + 1, list);
delete[] str; dbg.getCompletions(str + lastDelimPos + 1, list);
if(list.size() < 1)
break; break;
}
// TODO - perhaps use strings instead of char pointers // TODO: sort completions (add sort method to StringList)
completionList = cart.getCompletions().c_str(); completionList = list[0];
prefix = cart.getCompletionPrefix().c_str(); 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) // add to buffer as though user typed it (plus a space)
_currentPos = _promptStartPos + lastDelimPos + 1; _currentPos = _promptStartPos + lastDelimPos + 1;
while(*completionList != '\0') const char* clptr = completionList.c_str();
putcharIntern(*completionList++); while(*clptr != '\0')
putcharIntern(*clptr++);
putcharIntern(' '); putcharIntern(' ');
_promptEndPos = _currentPos; _promptEndPos = _currentPos;
@ -298,7 +303,6 @@ bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
print(prefix); print(prefix);
_promptEndPos = _currentPos; _promptEndPos = _currentPos;
} }
delete[] str;
dirty = true; dirty = true;
break; break;
} }
@ -883,3 +887,30 @@ bool PromptWidget::saveBuffer(string& filename)
out.close(); out.close();
return true; 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;
}
}

View File

@ -79,6 +79,10 @@ class PromptWidget : public Widget, public CommandSender
void loadConfig(); 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: private:
enum { enum {
kBufferSize = 32768, kBufferSize = 32768,