mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-18 21:30:38 +01:00
[apps/code] Autocomplete at the end of tokens
Not at the end of "words" separated by spaces.
This commit is contained in:
@@ -124,11 +124,21 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events:
|
||||
|
||||
VariableBoxController * EditorController::variableBoxForInputEventHandler(InputEventHandler * textInput) {
|
||||
VariableBoxController * varBox = App::app()->variableBoxController();
|
||||
/* If the editor should be autocompleting, the variable box has already been
|
||||
* loaded. We check shouldAutocomplete and not isAutocompleting, because the
|
||||
* autocompletion result might be empty. */
|
||||
if (!m_editorView.shouldAutocomplete()) {
|
||||
/* If the editor should be autocompleting an identifier, the variable box has
|
||||
* already been loaded. We check shouldAutocomplete and not isAutocompleting,
|
||||
* because the autocompletion result might be empty. */
|
||||
const char * beginningOfAutocompletion = nullptr;
|
||||
const char * cursor = nullptr;
|
||||
PythonTextArea::AutocompletionType autocompType = m_editorView.autocompletionType(&beginningOfAutocompletion, &cursor);
|
||||
if (autocompType == PythonTextArea::AutocompletionType::NoIdentifier) {
|
||||
varBox->loadFunctionsAndVariables(m_scriptIndex, nullptr, 0);
|
||||
} else if (autocompType == PythonTextArea::AutocompletionType::MiddleOfIdentifier) {
|
||||
varBox->empty();
|
||||
} else {
|
||||
assert(autocompType == PythonTextArea::AutocompletionType::EndOfIdentifier);
|
||||
assert(beginningOfAutocompletion != nullptr && cursor != nullptr);
|
||||
assert(cursor > beginningOfAutocompletion);
|
||||
varBox->loadFunctionsAndVariables(m_scriptIndex, beginningOfAutocompletion, cursor - beginningOfAutocompletion);
|
||||
}
|
||||
return varBox;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Code {
|
||||
class EditorView : public Responder, public View, public ScrollViewDelegate {
|
||||
public:
|
||||
EditorView(Responder * parentResponder, App * pythonDelegate);
|
||||
bool shouldAutocomplete() const { return m_textArea.shouldAutocomplete(); }
|
||||
PythonTextArea::AutocompletionType autocompletionType(const char ** autocompletionBeginning, const char ** autocompletionEnd) const { return m_textArea.autocompletionType(nullptr, autocompletionBeginning, autocompletionEnd); }
|
||||
bool isAutocompleting() const;
|
||||
void resetSelection();
|
||||
void setTextAreaDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) {
|
||||
@@ -19,6 +19,9 @@ public:
|
||||
void setText(char * textBuffer, size_t textBufferSize) {
|
||||
m_textArea.setText(textBuffer, textBufferSize);
|
||||
}
|
||||
const char * cursorLocation() {
|
||||
return m_textArea.cursorLocation();
|
||||
}
|
||||
bool setCursorLocation(const char * location) {
|
||||
return m_textArea.setCursorLocation(location);
|
||||
}
|
||||
|
||||
@@ -53,14 +53,64 @@ static inline size_t TokenLength(mp_lexer_t * lex, const char * tokenPosition) {
|
||||
}
|
||||
return lex->column - lex->tok_column;
|
||||
}
|
||||
bool PythonTextArea::shouldAutocomplete(const char * autocompletionLocation) const {
|
||||
if (isAutocompleting()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PythonTextArea::AutocompletionType PythonTextArea::autocompletionType(const char * autocompletionLocation, const char ** autocompletionLocationBeginning, const char ** autocompletionLocationEnd) const {
|
||||
const char * location = autocompletionLocation != nullptr ? autocompletionLocation : cursorLocation();
|
||||
CodePoint prevCodePoint = UTF8Helper::PreviousCodePoint(m_contentView.editedText(), location);
|
||||
return !UTF8Helper::CodePointIsEndOfWord(prevCodePoint)
|
||||
&& UTF8Helper::CodePointIsEndOfWord(UTF8Helper::CodePointAtLocation(location));
|
||||
const char * beginningOfToken = nullptr;
|
||||
|
||||
// We want to autocomplete only at the end of an identifier or a keyword
|
||||
AutocompletionType autocompleteType = AutocompletionType::NoIdentifier;
|
||||
nlr_buf_t nlr;
|
||||
if (nlr_push(&nlr) == 0) {
|
||||
const char * firstNonSpace = UTF8Helper::BeginningOfWord(m_contentView.editedText(), location);
|
||||
mp_lexer_t * lex = mp_lexer_new_from_str_len(0, firstNonSpace, UTF8Helper::EndOfWord(location) - firstNonSpace, 0);
|
||||
|
||||
const char * tokenStart;
|
||||
const char * tokenEnd;
|
||||
_mp_token_kind_t currentTokenKind = lex->tok_kind;
|
||||
|
||||
while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END) {
|
||||
tokenStart = firstNonSpace + lex->tok_column - 1;
|
||||
tokenEnd = tokenStart + TokenLength(lex, tokenStart);
|
||||
|
||||
if (location < tokenStart) {
|
||||
// The location for autocompletion is not in an identifier
|
||||
break;
|
||||
}
|
||||
if (currentTokenKind == MP_TOKEN_NAME
|
||||
|| (currentTokenKind >= MP_TOKEN_KW_FALSE
|
||||
&& currentTokenKind <= MP_TOKEN_KW_YIELD))
|
||||
{
|
||||
if (location < tokenEnd) {
|
||||
// The location for autocompletion is in the middle of an identifier
|
||||
autocompleteType = AutocompletionType::MiddleOfIdentifier;
|
||||
break;
|
||||
}
|
||||
if (location == tokenEnd) {
|
||||
// The location for autocompletion is at the end of an identifier
|
||||
beginningOfToken = tokenStart;
|
||||
autocompleteType = AutocompletionType::EndOfIdentifier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (location < tokenStart) {
|
||||
// The location for autocompletion is not in an identifier
|
||||
break;
|
||||
}
|
||||
mp_lexer_to_next(lex);
|
||||
currentTokenKind = lex->tok_kind;
|
||||
}
|
||||
mp_lexer_free(lex);
|
||||
nlr_pop();
|
||||
}
|
||||
if (autocompletionLocationBeginning != nullptr) {
|
||||
*autocompletionLocationBeginning = beginningOfToken;
|
||||
}
|
||||
if (autocompletionLocationEnd != nullptr) {
|
||||
*autocompletionLocationEnd = location;
|
||||
}
|
||||
assert(!isAutocompleting() || autocompleteType == AutocompletionType::EndOfIdentifier);
|
||||
return autocompleteType;
|
||||
}
|
||||
|
||||
const char * PythonTextArea::ContentView::textToAutocomplete() const {
|
||||
@@ -281,18 +331,18 @@ void PythonTextArea::removeAutocompletion() {
|
||||
void PythonTextArea::addAutocompletion() {
|
||||
assert(!m_contentView.isAutocompleting());
|
||||
|
||||
const char * autocompletionTokenBeginning = nullptr;
|
||||
const char * autocompletionLocation = const_cast<char *>(cursorLocation());
|
||||
const char * textToInsert = nullptr;
|
||||
int textToInsertLength = 0;
|
||||
if (shouldAutocomplete(autocompletionLocation)) {
|
||||
/* The previous code point is neither the beginning of the text, nor a
|
||||
* space, nor a \n, and the next code point is the end of the word.
|
||||
if (autocompletionType(autocompletionLocation, &autocompletionTokenBeginning) == AutocompletionType::EndOfIdentifier) {
|
||||
/* The cursor is at the end of an identifier.
|
||||
* 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();
|
||||
const char * beginningOfWord = m_contentView.textToAutocomplete();
|
||||
textToInsert = varBox->autocompletionForText(m_contentView.pythonDelegate()->menuController()->editedScriptIndex(), beginningOfWord, &textToInsertLength);
|
||||
const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex();
|
||||
textToInsert = varBox->autocompletionForText(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning, &textToInsertLength);
|
||||
}
|
||||
|
||||
// Try to insert the text (this might fail if the buffer is full)
|
||||
|
||||
@@ -9,6 +9,11 @@ class App;
|
||||
|
||||
class PythonTextArea : public TextArea {
|
||||
public:
|
||||
enum class AutocompletionType : uint8_t {
|
||||
EndOfIdentifier,
|
||||
MiddleOfIdentifier,
|
||||
NoIdentifier
|
||||
};
|
||||
PythonTextArea(Responder * parentResponder, App * pythonDelegate, const KDFont * font) :
|
||||
TextArea(parentResponder, &m_contentView, font),
|
||||
m_contentView(pythonDelegate, font)
|
||||
@@ -18,10 +23,14 @@ public:
|
||||
void unloadSyntaxHighlighter() { m_contentView.unloadSyntaxHighlighter(); }
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override;
|
||||
/* shouldAutocomplete returns true if there is currently autocompletion, or if
|
||||
* there should be autocompletion but there is not because there is no word to
|
||||
* autocomplete. */
|
||||
bool shouldAutocomplete(const char * autocompletionLocation = nullptr) const;
|
||||
/* autocompletionType returns:
|
||||
* - EndOfIdentifier if there is currently autocompletion, or if the wursor is
|
||||
* at the end of an identifier,
|
||||
* - MiddleOfIdentifier is the cursor is in the middle of an identifier,
|
||||
* - No identifier otherwise.
|
||||
* The autocompletionLocation can be provided with autocompletionLocation, or
|
||||
* retreived with autocompletionLocationBeginning and autocompletionLocationEnd. */
|
||||
AutocompletionType autocompletionType(const char * autocompletionLocation = nullptr, const char ** autocompletionLocationBeginning = nullptr, const char ** autocompletionLocationEnd = nullptr) const;
|
||||
bool isAutocompleting() const { return m_contentView.isAutocompleting(); }
|
||||
protected:
|
||||
class ContentView : public TextArea::ContentView {
|
||||
|
||||
@@ -190,16 +190,11 @@ void VariableBoxController::loadFunctionsAndVariables(int scriptIndex, const cha
|
||||
}
|
||||
}
|
||||
|
||||
const char * VariableBoxController::autocompletionForText(int scriptIndex, const char * textToAutocomplete, int * textToInsertLength) {
|
||||
/* The text to autocomplete will most probably not be null-terminated. We thus
|
||||
* say that the end of the text to autocomplete is the end if the current word,
|
||||
* determined by the next space char or the next null char.*/
|
||||
const char * endOfText = UTF8Helper::EndOfWord(textToAutocomplete);
|
||||
const int textLength = endOfText - textToAutocomplete;
|
||||
assert(textLength >= 1);
|
||||
const char * VariableBoxController::autocompletionForText(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength, int * textToInsertLength) {
|
||||
assert(textToAutocompleteLength >= 1);
|
||||
|
||||
// First load variables and functions that complete the textToAutocomplete
|
||||
loadFunctionsAndVariables(scriptIndex, textToAutocomplete, textLength);
|
||||
loadFunctionsAndVariables(scriptIndex, textToAutocomplete, textToAutocompleteLength);
|
||||
if (numberOfRows() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -212,10 +207,16 @@ const char * VariableBoxController::autocompletionForText(int scriptIndex, const
|
||||
currentNameLength = strlen(currentName);
|
||||
}
|
||||
// Assert the text we return does indeed autocomplete the text to autocomplete
|
||||
assert(currentNameLength != textLength && strncmp(textToAutocomplete, currentName, textLength) == 0);
|
||||
assert(currentNameLength != textToAutocompleteLength && strncmp(textToAutocomplete, currentName, textToAutocompleteLength) == 0);
|
||||
// Return the text without the beginning that matches the text to autocomplete
|
||||
*textToInsertLength = currentNameLength - textLength;
|
||||
return currentName + textLength;
|
||||
*textToInsertLength = currentNameLength - textToAutocompleteLength;
|
||||
return currentName + textToAutocompleteLength;
|
||||
}
|
||||
|
||||
void VariableBoxController::empty() {
|
||||
m_builtinNodesCount = 0;
|
||||
m_currentScriptNodesCount = 0;
|
||||
m_importedNodesCount = 0;
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
|
||||
@@ -35,7 +35,8 @@ public:
|
||||
|
||||
/* VariableBoxController */
|
||||
void loadFunctionsAndVariables(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength);
|
||||
const char * autocompletionForText(int scriptIndex, const char * textToAutocomplete, int * textToInsertLength);
|
||||
const char * autocompletionForText(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength, int * textToInsertLength);
|
||||
void empty();
|
||||
|
||||
private:
|
||||
//TODO LEA use size_t
|
||||
|
||||
Reference in New Issue
Block a user