Skip to content

Commit

Permalink
libshell: Adjusted autocompletion to insert common prefix first
Browse files Browse the repository at this point in the history
If all the possible completions have the same prefix, it will be
appended as the first completion.
  • Loading branch information
skyjake committed Mar 16, 2013
1 parent e046632 commit 49baf2b
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 11 deletions.
9 changes: 5 additions & 4 deletions doomsday/libdeng2/include/de/data/string.h
Expand Up @@ -203,14 +203,15 @@ class DENG2_PUBLIC String : public QString
dint compareWithoutCase(String const &str, int n) const;

/**
* Compares two strings (case sensitive) to see how many characters they
* have in common starting from the left.
* Compares two strings to see how many characters they have in common
* starting from the left.
*
* @param str String.
* @param str String.
* @param sensitivity Case sensitivity.
*
* @return Number of characters the two strings have in common from the left.
*/
int commonPrefixLength(String const &str) const;
int commonPrefixLength(String const &str, Qt::CaseSensitivity sensitivity = Qt::CaseSensitive) const;

/**
* Converts the string to UTF-8 and returns it as a byte block.
Expand Down
11 changes: 9 additions & 2 deletions doomsday/libdeng2/src/data/string.cpp
Expand Up @@ -354,13 +354,20 @@ dint String::compareWithoutCase(const String &str, int n) const
return left(n).compare(str.left(n), Qt::CaseInsensitive);
}

int String::commonPrefixLength(String const &str) const
int String::commonPrefixLength(String const &str, Qt::CaseSensitivity sensitivity) const
{
int count = 0;
int len = qMin(str.size(), size());
for(int i = 0; i < len; ++i, ++count)
{
if(at(i) != str.at(i)) break;
if(sensitivity == Qt::CaseSensitive)
{
if(at(i) != str.at(i)) break;
}
else
{
if(at(i).toLower() != str.at(i).toLower()) break;
}
}
return count;
}
Expand Down
54 changes: 49 additions & 5 deletions doomsday/libshell/src/lineeditwidget.cpp
Expand Up @@ -214,14 +214,27 @@ DENG2_PIMPL(LineEditWidget)
return word;
}

QList<String> completionsForBase(String base) const
QList<String> completionsForBase(String base, String &commonPrefix) const
{
bool first = true;
QList<String> suggestions;
foreach(String term, lexicon.terms())
{
if(term.startsWith(base, Qt::CaseInsensitive) && term.size() > base.size())
{
suggestions.append(term);

// Determine if all the suggestions have a common prefix.
if(first)
{
commonPrefix = term;
first = false;
}
else if(!commonPrefix.isEmpty())
{
int len = commonPrefix.commonPrefixLength(term, Qt::CaseInsensitive);
commonPrefix = commonPrefix.left(len);
}
}
}
qSort(suggestions);
Expand All @@ -236,7 +249,18 @@ DENG2_PIMPL(LineEditWidget)
if(!base.isEmpty())
{
// Find all the possible completions and apply the first one.
suggestions = completionsForBase(base);
String commonPrefix;
suggestions = completionsForBase(base, commonPrefix);
if(!commonPrefix.isEmpty() && commonPrefix != base)
{
completion.ordinal = -1;
commonPrefix.remove(0, base.size());
completion.pos = cursor;
completion.size = commonPrefix.size();
text.insert(cursor, commonPrefix);
cursor += completion.size;
return true;
}
if(!suggestions.isEmpty())
{
completion.ordinal = (forwardCycle? 0 : suggestions.size() - 1);
Expand All @@ -256,9 +280,23 @@ DENG2_PIMPL(LineEditWidget)
cursor = completion.pos;
String const base = wordBehindCursor();

// Go to next suggestion.
completion.ordinal = de::wrap(completion.ordinal + (forwardCycle? 1 : -1),
0, suggestions.size());
if(completion.ordinal < 0)
{
// This occurs after a common prefix is inserted rather than
// a full suggestion.
completion.ordinal = (forwardCycle? 0 : suggestions.size() - 1);

if(base + text.mid(completion.pos, completion.size) == suggestions[completion.ordinal])
{
// We already had this one, skip it.
cycleCompletion(forwardCycle);
}
}
else
{
cycleCompletion(forwardCycle);
}

String comp = suggestions[completion.ordinal];
comp.remove(0, base.size());

Expand All @@ -271,6 +309,12 @@ DENG2_PIMPL(LineEditWidget)
return false;
}

void cycleCompletion(bool forwardCycle)
{
completion.ordinal = de::wrap(completion.ordinal + (forwardCycle? 1 : -1),
0, suggestions.size());
}

void acceptCompletion()
{
completion.reset();
Expand Down

0 comments on commit 49baf2b

Please sign in to comment.