diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index 4169e359e..c9563090f 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -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(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(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(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(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) { diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index 037069446..5ede682b8 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -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; }; } diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index c2a365e08..c6d89e7f7 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -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; diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 591d8d326..22ea76937 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -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: