[apps/code] Cycle through possible autocompletions with up/down

This commit is contained in:
Léa Saviot
2020-04-24 17:53:13 +02:00
committed by Émilie Feral
parent afdf34bbb9
commit 2a823419ff
4 changed files with 77 additions and 23 deletions

View File

@@ -285,6 +285,12 @@ bool PythonTextArea::handleEvent(Ion::Events::Event event) {
|| event == Ion::Events::Alpha
|| event == Ion::Events::OnOff)
{
} else if(event == Ion::Events::Up
|| event == Ion::Events::Down)
{
//TODO LEA handle only one suggestion in var box.
cycleAutocompletion(event == Ion::Events::Down);
return true;
} else {
removeAutocompletion();
m_contentView.reloadRectFromPosition(m_contentView.cursorLocation(), false);
@@ -318,6 +324,12 @@ bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bo
}
void PythonTextArea::removeAutocompletion() {
assert(m_contentView.isAutocompleting());
removeAutocompletionText();
m_contentView.setAutocompleting(false);
}
void PythonTextArea::removeAutocompletionText() {
assert(m_contentView.isAutocompleting());
assert(m_contentView.autocompletionEnd() != nullptr);
const char * autocompleteStart = m_contentView.cursorLocation();
@@ -326,7 +338,6 @@ void PythonTextArea::removeAutocompletion() {
//TODO LEA if (autocompleteEnd > autocompleteStart) {
m_contentView.removeText(autocompleteStart, autocompleteEnd);
//TODO LEA }
m_contentView.setAutocompleting(false);
}
void PythonTextArea::addAutocompletion() {
@@ -337,33 +348,52 @@ void PythonTextArea::addAutocompletion() {
// The cursor is not at the end of an identifier.
return;
}
/* Compute the text to insert:
* Look first in the current script variables and functions, then in the
* builtins, then in the imported modules/scripts. */
VariableBoxController * varBox = m_contentView.pythonDelegate()->variableBoxController();
// First load variables and functions that complete the textToAutocomplete
const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex();
m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning);
if (addAutocompletionTextAtIndex(0)) {
m_contentView.setAutocompleting(true);
}
}
bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) {
// The variable box should be loaded at this point
const char * autocompletionTokenBeginning = nullptr;
const char * autocompletionLocation = const_cast<char *>(cursorLocation());
AutocompletionType type = autocompletionType(autocompletionLocation, &autocompletionTokenBeginning); // Done to get autocompletionTokenBeginning
assert(type == AutocompletionType::EndOfIdentifier);
(void)type; // Silence warnings
VariableBoxController * varBox = m_contentView.pythonDelegate()->variableBoxController();
int textToInsertLength = 0;
bool addParentheses = false;
const char * textToInsert = varBox->autocompletionForText(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning, &textToInsertLength, &addParentheses);
const char * textToInsert = varBox->autocompletionAlternativeAtIndex(autocompletionLocation - autocompletionTokenBeginning, &textToInsertLength, &addParentheses, nextIndex, currentIndexToUpdate);
// Try to insert the text (this might fail if the buffer is full)
if (textToInsert != nullptr
&& textToInsertLength > 0
&& m_contentView.insertTextAtLocation(textToInsert, const_cast<char *>(autocompletionLocation), textToInsertLength))
{
m_contentView.setAutocompleting(true);
autocompletionLocation += textToInsertLength;
m_contentView.setAutocompletionEnd(autocompletionLocation);
// Try to insert the parentheses if needed text
const char * parentheses = ScriptNodeCell::k_parentheses;
constexpr int parenthesesLength = 2;
assert(strlen(parentheses) == parenthesesLength);
if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast<char *>(autocompletionLocation), parenthesesLength)) {
m_contentView.setAutocompleting(true);
m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength);
}
return true;
}
// Try to insert the parentheses if needed text
const char * parentheses = ScriptNodeCell::k_parentheses;
constexpr int parenthesesLength = 2;
assert(strlen(parentheses) == parenthesesLength);
if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast<char *>(autocompletionLocation), parenthesesLength)) {
m_contentView.setAutocompleting(true);
m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength);
}
return false;
}
void PythonTextArea::cycleAutocompletion(bool downwards) {
assert(m_contentView.isAutocompleting());
removeAutocompletionText();
addAutocompletionTextAtIndex(m_autocompletionResultIndex + (downwards ? 1 : -1), &m_autocompletionResultIndex);
}
void PythonTextArea::acceptAutocompletion(bool moveCursorToEndOfAutocompletion) {

View File

@@ -16,7 +16,8 @@ public:
};
PythonTextArea(Responder * parentResponder, App * pythonDelegate, const KDFont * font) :
TextArea(parentResponder, &m_contentView, font),
m_contentView(pythonDelegate, font)
m_contentView(pythonDelegate, font),
m_autocompletionResultIndex(0)
{
}
void loadSyntaxHighlighter() { m_contentView.loadSyntaxHighlighter(); }
@@ -60,10 +61,14 @@ protected:
};
private:
void removeAutocompletion();
void removeAutocompletionText(); // Just removes the suggested text, not the autocompletion mode
void addAutocompletion();
bool addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate = nullptr); // Assumes the var box is already loaded
void cycleAutocompletion(bool downwards);
void acceptAutocompletion(bool moveCursorToEndOfAutocompletion);
const ContentView * nonEditableContentView() const override { return &m_contentView; }
ContentView m_contentView;
int m_autocompletionResultIndex;
};
}

View File

@@ -200,18 +200,36 @@ const char * VariableBoxController::autocompletionForText(int scriptIndex, const
return nullptr;
}
// Return the first node
ScriptNode * node = scriptNodeAtIndex(0);
return autocompletionAlternativeAtIndex(textToAutocompleteLength, textToInsertLength, addParentheses, 0);
}
const char * VariableBoxController::autocompletionAlternativeAtIndex(int textToAutocompleteLength, int * textToInsertLength, bool * addParentheses, int index, int * indexToUpdate) {
assert(numberOfRows() != 0);
int nodesCount = 0; // We cannot use numberOfRows as it contains the banners
NodeOrigin origins[] = {NodeOrigin::CurrentScript, NodeOrigin::Builtins, NodeOrigin::Importation};
for (NodeOrigin origin : origins) {
nodesCount += nodesCountForOrigin(origin);
}
if (index < 0) {
assert(index == -1);
index = nodesCount - 1;
} else if (index >= nodesCount) {
assert(index == nodesCount);
index = 0;
}
if (indexToUpdate != nullptr) {
*indexToUpdate = index;
}
ScriptNode * node = scriptNodeAtIndex(index);
const char * currentName = node->name();
int currentNameLength = node->nameLength();
if (currentNameLength < 0) {
currentNameLength = strlen(currentName);
}
*addParentheses = node->type() == ScriptNode::Type::WithParentheses;
// Assert the text we return does indeed autocomplete the text to autocomplete
assert((*addParentheses
|| currentNameLength != textToAutocompleteLength)
&& strncmp(textToAutocomplete, currentName, textToAutocompleteLength) == 0);
// Return the text without the beginning that matches the text to autocomplete
*textToInsertLength = currentNameLength - textToAutocompleteLength;
return currentName + textToAutocompleteLength;

View File

@@ -36,6 +36,7 @@ public:
/* VariableBoxController */
void loadFunctionsAndVariables(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength);
const char * autocompletionForText(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength, int * textToInsertLength, bool * addParentheses);
const char * autocompletionAlternativeAtIndex(int textToAutocompleteLength, int * textToInsertLength, bool * addParentheses, int index, int * indexToUpdate = nullptr);
void empty();
private: