in ext/linenoise-ng/src/linenoise.cpp [2160:2441]
int InputBuffer::completeLine(PromptBase &pi) {
linenoiseCompletions lc;
char32_t c = 0;
// completionCallback() expects a parsable entity, so find the previous break
// character and
// extract a copy to parse. we also handle the case where tab is hit while
// not at end-of-line.
int startIndex = pos;
while (--startIndex >= 0) {
if (strchr(breakChars, buf32[startIndex])) {
break;
}
}
++startIndex;
int itemLength = pos - startIndex;
Utf8String bufferContents(Utf32String(buf32, pos));
// get a list of completions and the position to start completing from
completionCallback(bufferContents.get(), &startIndex, &lc);
itemLength = pos - startIndex;
Utf32String original(buf32 + startIndex, itemLength);
// if no completions, we are done
if (lc.completionStrings.size() == 0) {
beep();
freeCompletions(&lc);
return 0;
}
#ifdef CYCLE_COMPLETIONS
// Instead of the default handling that will display all possibilities
// when completion is ambiguous, we implement an alternative model where:
// - hitting tab always displays 1st choice
// - hitting tab again will cycle to the next possibility
// - after the last possibility, goes back to the original text
// - hitting esc cancels and reverts original text from user
// - hitting any other key commits and exits the cycling
// TODO(alfredo) - make Esc key work (currently being eaten by esc processing)
int activeChoice = 0;
bool done = false;
size_t oldTextLength = itemLength;
while (!done) {
activeChoice = (activeChoice + 1) % (lc.completionStrings.size() + 1);
if (activeChoice == 0) {
// show original
if (!displayCompletion(startIndex, &oldTextLength, original)) beep();
refreshLine(pi);
} else {
if (!displayCompletion(startIndex, &oldTextLength,
lc.completionStrings[activeChoice - 1]))
beep();
refreshLine(pi);
}
// we can't complete any further, wait for second tab
do {
c = linenoiseReadChar();
c = cleanupCtrl(c);
} while (c == static_cast<char32_t>(-1));
// if any character other than tab, pass it to the main loop
switch (c) {
case ctrlChar('I'):
// let it cycle to next option
break;
case 0x4000006e: // ESC
beep();
// abort
done = true;
if (!displayCompletion(startIndex, &oldTextLength, original)) beep();
refreshLine(pi);
break;
default:
done = true;
break;
}
}
freeCompletions(&lc);
return 0;
#else // !CYCLE_COMPLETIONS
// at least one completion
int longestCommonPrefix = 0;
int displayLength = 0;
if (lc.completionStrings.size() == 1) {
longestCommonPrefix = static_cast<int>(lc.completionStrings[0].length());
} else {
bool keepGoing = true;
while (keepGoing) {
for (size_t j = 0; j < lc.completionStrings.size() - 1; ++j) {
char32_t c1 = lc.completionStrings[j][longestCommonPrefix];
char32_t c2 = lc.completionStrings[j + 1][longestCommonPrefix];
if ((0 == c1) || (0 == c2) || (towlower(c1) != towlower(c2))) {
keepGoing = false;
break;
}
}
if (keepGoing) {
++longestCommonPrefix;
}
}
}
if (lc.completionStrings.size() != 1) { // beep if ambiguous
beep();
}
// if we can extend the item, extend it and return to main loop
if (longestCommonPrefix > itemLength) {
displayLength = len + longestCommonPrefix - itemLength;
if (displayLength > buflen) {
longestCommonPrefix -= displayLength - buflen; // don't overflow buffer
displayLength = buflen; // truncate the insertion
beep(); // and make a noise
}
Utf32String displayText(displayLength + 1);
memcpy(displayText.get(), buf32, sizeof(char32_t) * startIndex);
memcpy(&displayText[startIndex], &lc.completionStrings[0][0],
sizeof(char32_t) * longestCommonPrefix);
int tailIndex = startIndex + longestCommonPrefix;
memcpy(&displayText[tailIndex], &buf32[pos],
sizeof(char32_t) * (displayLength - tailIndex + 1));
copyString32(buf32, displayText.get(), displayLength);
pos = startIndex + longestCommonPrefix;
len = displayLength;
refreshLine(pi);
return 0;
}
// we can't complete any further, wait for second tab
do {
c = linenoiseReadChar();
c = cleanupCtrl(c);
} while (c == static_cast<char32_t>(-1));
// if any character other than tab, pass it to the main loop
if (c != ctrlChar('I')) {
freeCompletions(&lc);
return c;
}
// we got a second tab, maybe show list of possible completions
bool showCompletions = true;
bool onNewLine = false;
if (lc.completionStrings.size() > completionCountCutoff) {
int savePos =
pos; // move cursor to EOL to avoid overwriting the command line
pos = len;
refreshLine(pi);
pos = savePos;
printf("\nDisplay all %u possibilities? (y or n)",
static_cast<unsigned int>(lc.completionStrings.size()));
fflush(stdout);
onNewLine = true;
while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != ctrlChar('C')) {
do {
c = linenoiseReadChar();
c = cleanupCtrl(c);
} while (c == static_cast<char32_t>(-1));
}
switch (c) {
case 'n':
case 'N':
showCompletions = false;
freeCompletions(&lc);
break;
case ctrlChar('C'):
showCompletions = false;
freeCompletions(&lc);
if (write(1, "^C", 2) == -1) return -1; // Display the ^C we got
c = 0;
break;
}
}
// if showing the list, do it the way readline does it
bool stopList = false;
if (showCompletions) {
int longestCompletion = 0;
for (size_t j = 0; j < lc.completionStrings.size(); ++j) {
itemLength = static_cast<int>(lc.completionStrings[j].length());
if (itemLength > longestCompletion) {
longestCompletion = itemLength;
}
}
longestCompletion += 2;
int columnCount = pi.promptScreenColumns / longestCompletion;
if (columnCount < 1) {
columnCount = 1;
}
if (!onNewLine) { // skip this if we showed "Display all %d possibilities?"
int savePos =
pos; // move cursor to EOL to avoid overwriting the command line
pos = len;
refreshLine(pi);
pos = savePos;
}
size_t pauseRow = getScreenRows() - 1;
size_t rowCount =
(lc.completionStrings.size() + columnCount - 1) / columnCount;
for (size_t row = 0; row < rowCount; ++row) {
if (row == pauseRow) {
printf("\n--More--");
fflush(stdout);
c = 0;
bool doBeep = false;
while (c != ' ' && c != '\r' && c != '\n' && c != 'y' && c != 'Y' &&
c != 'n' && c != 'N' && c != 'q' && c != 'Q' &&
c != ctrlChar('C')) {
if (doBeep) {
beep();
}
doBeep = true;
do {
c = linenoiseReadChar();
c = cleanupCtrl(c);
} while (c == static_cast<char32_t>(-1));
}
switch (c) {
case ' ':
case 'y':
case 'Y':
printf("\r \r");
pauseRow += getScreenRows() - 1;
break;
case '\r':
case '\n':
printf("\r \r");
++pauseRow;
break;
case 'n':
case 'N':
case 'q':
case 'Q':
printf("\r \r");
stopList = true;
break;
case ctrlChar('C'):
if (write(1, "^C", 2) == -1) return -1; // Display the ^C we got
stopList = true;
break;
}
} else {
printf("\n");
}
if (stopList) {
break;
}
for (int column = 0; column < columnCount; ++column) {
size_t index = (column * rowCount) + row;
if (index < lc.completionStrings.size()) {
itemLength = static_cast<int>(lc.completionStrings[index].length());
fflush(stdout);
if (write32(1, lc.completionStrings[index].get(), itemLength) == -1)
return -1;
if (((column + 1) * rowCount) + row < lc.completionStrings.size()) {
for (int k = itemLength; k < longestCompletion; ++k) {
printf(" ");
}
}
}
}
}
fflush(stdout);
freeCompletions(&lc);
}
// display the prompt on a new line, then redisplay the input buffer
if (!stopList || c == ctrlChar('C')) {
if (write(1, "\n", 1) == -1) return 0;
}
if (!pi.write()) return 0;
#ifndef _WIN32
// we have to generate our own newline on line wrap on Linux
if (pi.promptIndentation == 0 && pi.promptExtraLines > 0)
if (write(1, "\n", 1) == -1) return 0;
#endif
pi.promptCursorRowOffset = pi.promptExtraLines;
refreshLine(pi);
#endif // !CYCLE_COMPLETIONS
return 0;
}