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>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>

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
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;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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

View File

@ -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
{

View File

@ -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
*/

View File

@ -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();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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,