From aa9593b8cb2d24bc1cf99b0ff56a366d643028a0 Mon Sep 17 00:00:00 2001 From: Anatoly Trosinenko Date: Sun, 17 Feb 2019 13:09:29 +0300 Subject: [PATCH 1/3] Implement simple completer for Modelica annotations (task#5333) Fetches completions from OpenModelica.AutoCompletion.Annotations --- OMEdit/OMEditGUI/Editors/BaseEditor.cpp | 5 + OMEdit/OMEditGUI/Editors/BaseEditor.h | 1 + OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp | 117 ++++++++++++++++++ OMEdit/OMEditGUI/Editors/ModelicaEditor.h | 4 + OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.h | 2 +- .../Resources/icons/completerAnnotation.svg | 63 ++++++++++ OMEdit/OMEditGUI/resource_omedit.qrc | 1 + 7 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 OMEdit/OMEditGUI/Resources/icons/completerAnnotation.svg diff --git a/OMEdit/OMEditGUI/Editors/BaseEditor.cpp b/OMEdit/OMEditGUI/Editors/BaseEditor.cpp index 0f3f91932..c788953fb 100644 --- a/OMEdit/OMEditGUI/Editors/BaseEditor.cpp +++ b/OMEdit/OMEditGUI/Editors/BaseEditor.cpp @@ -769,6 +769,11 @@ CompleterItem::CompleterItem(const QString &key, const QString &value, const QSt } } +CompleterItem::CompleterItem(const QString &key, const QString &value, const QString &select, const QString &description) + : mKey(key), mValue(value), mSelect(select), mDescription(description) +{ +} + CompleterItem::CompleterItem(const QString &value, const QString &description) : mKey(value), mValue(value), mSelect(value), mDescription(description) { diff --git a/OMEdit/OMEditGUI/Editors/BaseEditor.h b/OMEdit/OMEditGUI/Editors/BaseEditor.h index 3cc691f75..8ecbc6ef3 100644 --- a/OMEdit/OMEditGUI/Editors/BaseEditor.h +++ b/OMEdit/OMEditGUI/Editors/BaseEditor.h @@ -215,6 +215,7 @@ class CompleterItem public: CompleterItem() {} CompleterItem(const QString &key, const QString &value, const QString &select); + CompleterItem(const QString &key, const QString &value, const QString &select, const QString &description); CompleterItem(const QString &value, const QString &description); QString mKey; QString mValue; diff --git a/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp b/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp index d75c15f93..ba888801b 100644 --- a/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp +++ b/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp @@ -93,6 +93,10 @@ void ModelicaEditor::popUpCompleter() mpPlainTextEdit->insertCompleterSymbols(classes, ":/Resources/icons/completerClass.svg"); mpPlainTextEdit->insertCompleterSymbols(components, ":/Resources/icons/completerComponent.svg"); + QList annotations; + getCompletionAnnotations(stringAfterWord("annotation"), annotations); + mpPlainTextEdit->insertCompleterSymbols(annotations, ":/Resources/icons/completerAnnotation.svg"); + QCompleter *completer = mpPlainTextEdit->completer(); QRect cr = mpPlainTextEdit->cursorRect(); cr.setWidth(completer->popup()->sizeHintForColumn(0)+ completer->popup()->verticalScrollBar()->sizeHint().width()); @@ -165,6 +169,22 @@ QString ModelicaEditor::wordUnderCursor() return mpPlainTextEdit->document()->toPlainText().mid(begin, end - begin); } +/*! + * \brief Returns the substring from the last occurrence of `word` to the cursor position + * \param word Starting word of the substring + * \return Resulting substring or Null QString if no `word` occurrence found up to the cursor position + */ +QString ModelicaEditor::stringAfterWord(const QString &word) +{ + int pos = mpPlainTextEdit->textCursor().position(); + QString plainText = mpPlainTextEdit->document()->toPlainText(); + int index = plainText.lastIndexOf(word, pos); + if (index == -1) + return QString(); + else + return plainText.mid(index, pos - index); +} + void ModelicaEditor::getCompletionSymbols(QString word, QList &classes, QList &components) { QStringList nameComponents = word.split('.'); @@ -183,6 +203,103 @@ void ModelicaEditor::getCompletionSymbols(QString word, QList &cl } } +/*! + * \brief Looks up the root for annotation auto completion information + */ +LibraryTreeItem *ModelicaEditor::getAnnotationCompletionRoot() +{ + LibraryTreeItem *pLibraryRoot = MainWindow::instance()->getLibraryWidget()->getLibraryTreeModel()->getRootLibraryTreeItem(); + LibraryTreeItem *pModelicaReference = 0; + + for (int i = 0; i < pLibraryRoot->childrenSize(); ++i) { + if (pLibraryRoot->childAt(i)->getName() == "OpenModelica") + pModelicaReference = pLibraryRoot->childAt(i); + } + + if (pModelicaReference) { + return deepResolve(pModelicaReference, QStringList() << "AutoCompletion" << "Annotations"); + } else { + return 0; + } +} + +/*! + * \brief Returns a collection of completion items for the parsed `stack` of nested annotations + * \param stack A stack of nested annotations (f.e. "annotation(uses(Modelica(ver|" becomes ["uses", "Modelica"] + * \param annotations Resulting collection of compeltion items + */ +void ModelicaEditor::getCompletionAnnotations(const QStringList &stack, QList &annotations) +{ + LibraryTreeItem *pReference = getAnnotationCompletionRoot(); + if (pReference) { + LibraryTreeItem *pAnnotation = deepResolve(pReference, stack); + if (pAnnotation) { + for (int i = 0; i < pAnnotation->childrenSize(); ++i) { + QString name = pAnnotation->childAt(i)->getName(); + annotations << CompleterItem(name, name + "(", name, pAnnotation->childAt(i)->getHTMLDescription()); + } + QList components = pAnnotation->getComponentsList(); + for (int i = 0; i < components.size(); ++i) { + annotations << CompleterItem(components[i]->getName() + " = ", components[i]->getHTMLDescription()); + } + } + } +} + +/*! + * \brief Resolves the annotation under cursor as a stack of nested names and returns completions + * \param str A string starting with the "annotation" word up to the cursor position + * \param annotations Resulting collection of completion items + */ +void ModelicaEditor::getCompletionAnnotations(const QString &str, QList &annotations) +{ + QStringList stack; + int lastWordStart = 0; + bool insideWord = false; + + if (str.isEmpty()) + return; + + for (int i = 0; i < str.size(); ++i) { + QChar ch = str[i]; + + // First, handle string literals + if (ch == '"') { + for (++i; i < str.size() && str[i] != '"'; ++i) { + if (str[i] == '\\') + ++i; + } + // skipped, restarting as usual + --i; + continue; + } + + // Now, handle the stack of annotations + if (ch == '(') { + stack << str.mid(lastWordStart, i - lastWordStart).trimmed(); + } + if (ch == ')') { + if (stack.isEmpty()) { + return; // not in an annotation at all + } + stack.pop_back(); + } + + // Last, account for boundaries of words to be placed in stack + bool partOfLiteral = ch.isLetterOrNumber() || ch == '_'; + if (!insideWord && partOfLiteral) { + lastWordStart = i; + } + insideWord = partOfLiteral; + } + + if (stack.isEmpty()) { + return; + } + stack.pop_front(); // pop 'annotation' + getCompletionAnnotations(stack, annotations); +} + /*! * \brief ModelicaEditor::getClassNames * Uses the OMC parseString API to check the class names inside the Modelica Text diff --git a/OMEdit/OMEditGUI/Editors/ModelicaEditor.h b/OMEdit/OMEditGUI/Editors/ModelicaEditor.h index a93d3b1c1..982f11777 100644 --- a/OMEdit/OMEditGUI/Editors/ModelicaEditor.h +++ b/OMEdit/OMEditGUI/Editors/ModelicaEditor.h @@ -59,10 +59,14 @@ class ModelicaEditor : public BaseEditor bool isTextChanged() {return mTextChanged;} virtual void popUpCompleter(); QString wordUnderCursor(); + QString stringAfterWord(const QString &word); static LibraryTreeItem *deepResolve(LibraryTreeItem *pItem, QStringList nameComponents); QList getCandidateContexts(QStringList nameComponents); static void tryToCompleteInSingleContext(QStringList &result, LibraryTreeItem *pItem, QString lastPart); void getCompletionSymbols(QString word, QList &classes, QList &components); + LibraryTreeItem *getAnnotationCompletionRoot(); + void getCompletionAnnotations(const QStringList &stack, QList &annotations); + void getCompletionAnnotations(const QString &str, QList &annotations); static QList getCodeSnippets(); private: QString mLastValidText; diff --git a/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.h b/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.h index 157aa5734..4b59df4b2 100644 --- a/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.h +++ b/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.h @@ -191,6 +191,7 @@ class LibraryTreeItem : public QObject OMCInterface::getClassInformation_res mClassInformation; SimulationOptions mSimulationOptions; OMSSimulationOptions mOMSSimulationOptions; + const QList &getComponentsList(); private: bool mIsRootItem; LibraryTreeItem *mpParentLibraryTreeItem; @@ -198,7 +199,6 @@ class LibraryTreeItem : public QObject QList mInheritedClasses; QList mComponents; bool mComponentsLoaded; - const QList &getComponentsList(); LibraryType mLibraryType; bool mSystemLibrary; ModelWidget *mpModelWidget; diff --git a/OMEdit/OMEditGUI/Resources/icons/completerAnnotation.svg b/OMEdit/OMEditGUI/Resources/icons/completerAnnotation.svg new file mode 100644 index 000000000..6a4e98f9d --- /dev/null +++ b/OMEdit/OMEditGUI/Resources/icons/completerAnnotation.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/OMEdit/OMEditGUI/resource_omedit.qrc b/OMEdit/OMEditGUI/resource_omedit.qrc index c603e8b04..afa6c7bb6 100644 --- a/OMEdit/OMEditGUI/resource_omedit.qrc +++ b/OMEdit/OMEditGUI/resource_omedit.qrc @@ -209,5 +209,6 @@ Resources/icons/instantiate.svg Resources/icons/completerComponent.svg Resources/icons/completerClass.svg + Resources/icons/completerAnnotation.svg From 3a169b48b7062a9bb5ccf0c06f073da8bb8649c5 Mon Sep 17 00:00:00 2001 From: Adeel Asghar Date: Thu, 21 Mar 2019 14:49:57 +0100 Subject: [PATCH 2/3] Don't try to fetch the components of root LibraryTreeItem Use the current cursor position instead of end of word in PlainTextEdit::insertCompletionItem Some other minor improvements --- OMEdit/OMEditGUI/Editors/BaseEditor.cpp | 20 ++++++++++--------- OMEdit/OMEditGUI/Editors/BaseEditor.h | 2 +- OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp | 7 ++++++- OMEdit/OMEditGUI/Editors/ModelicaEditor.h | 2 +- .../OMEditGUI/Modeling/LibraryTreeWidget.cpp | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/OMEdit/OMEditGUI/Editors/BaseEditor.cpp b/OMEdit/OMEditGUI/Editors/BaseEditor.cpp index c788953fb..53c6470d7 100644 --- a/OMEdit/OMEditGUI/Editors/BaseEditor.cpp +++ b/OMEdit/OMEditGUI/Editors/BaseEditor.cpp @@ -1584,7 +1584,6 @@ void PlainTextEdit::insertCompletionItem(const QModelIndex &index) QTextCursor cursor = textCursor(); cursor.beginEditBlock(); int extra = completionlength[0].length() - mpCompleter->completionPrefix().length(); - cursor.movePosition(QTextCursor::EndOfWord); cursor.insertText(completionlength[0].right(extra)); // store the cursor position to be used for selecting text when inserting code snippets int currentpos = cursor.position(); @@ -1611,13 +1610,6 @@ void PlainTextEdit::insertCompletionItem(const QModelIndex &index) setTextCursor(cursor); } -QString PlainTextEdit::textUnderCursor() const -{ - QTextCursor cursor = textCursor(); - cursor.select(QTextCursor::WordUnderCursor); - return cursor.selectedText(); -} - /*! * \brief PlainTextEdit::keyPressEvent * Reimplementation of keyPressEvent. @@ -1726,7 +1718,7 @@ void PlainTextEdit::keyPressEvent(QKeyEvent *pEvent) static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word bool hasModifier = (pEvent->modifiers() != Qt::NoModifier) && !ctrlOrShift; - QString completionPrefix = textUnderCursor(); + QString completionPrefix = mpBaseEditor->wordUnderCursor(); if ((!isCompleterShortcut && !isCompleterChar) && (hasModifier || pEvent->text().isEmpty()|| completionPrefix.length() < 1 || eow.contains(pEvent->text().right(1)))) { mpCompleter->popup()->hide(); return; @@ -1982,6 +1974,16 @@ BaseEditor::BaseEditor(QWidget *pParent) initialize(); } +/*! + * \brief BaseEditor::wordUnderCursor + */ +QString BaseEditor::wordUnderCursor() +{ + QTextCursor cursor = mpPlainTextEdit->textCursor(); + cursor.select(QTextCursor::WordUnderCursor); + return cursor.selectedText(); +} + /*! * \brief BaseEditor::initialize * Initializes the editor with default values. diff --git a/OMEdit/OMEditGUI/Editors/BaseEditor.h b/OMEdit/OMEditGUI/Editors/BaseEditor.h index 8ecbc6ef3..a834c5668 100644 --- a/OMEdit/OMEditGUI/Editors/BaseEditor.h +++ b/OMEdit/OMEditGUI/Editors/BaseEditor.h @@ -287,7 +287,6 @@ class PlainTextEdit : public QPlainTextEdit void foldOrUnfold(bool unFold); void handleHomeKey(bool keepAnchor); void toggleBlockVisible(const QTextBlock &block); - QString textUnderCursor() const; private slots: void showCompletionItemToolTip(const QModelIndex & index); void insertCompletionItem(const QModelIndex & index); @@ -331,6 +330,7 @@ class BaseEditor : public QWidget DocumentMarker* getDocumentMarker() {return mpDocumentMarker;} void setForceSetPlainText(bool forceSetPlainText) {mForceSetPlainText = forceSetPlainText;} virtual void popUpCompleter () = 0; + virtual QString wordUnderCursor(); private: void initialize(); void createActions(); diff --git a/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp b/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp index ba888801b..cd71f5419 100644 --- a/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp +++ b/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp @@ -155,6 +155,10 @@ QList ModelicaEditor::getCandidateContexts(QStringList nameCom return result; } +/*! + * \brief ModelicaEditor::wordUnderCursor + * \return + */ QString ModelicaEditor::wordUnderCursor() { int end = mpPlainTextEdit->textCursor().position(); @@ -240,7 +244,8 @@ void ModelicaEditor::getCompletionAnnotations(const QStringList &stack, QList components = pAnnotation->getComponentsList(); for (int i = 0; i < components.size(); ++i) { - annotations << CompleterItem(components[i]->getName() + " = ", components[i]->getHTMLDescription()); + QString componentName = components[i]->getName(); + annotations << CompleterItem(componentName, componentName + " = ", componentName, components[i]->getHTMLDescription()); } } } diff --git a/OMEdit/OMEditGUI/Editors/ModelicaEditor.h b/OMEdit/OMEditGUI/Editors/ModelicaEditor.h index 982f11777..3962f79fe 100644 --- a/OMEdit/OMEditGUI/Editors/ModelicaEditor.h +++ b/OMEdit/OMEditGUI/Editors/ModelicaEditor.h @@ -58,7 +58,7 @@ class ModelicaEditor : public BaseEditor void setTextChanged(bool changed) {mTextChanged = changed;} bool isTextChanged() {return mTextChanged;} virtual void popUpCompleter(); - QString wordUnderCursor(); + virtual QString wordUnderCursor(); QString stringAfterWord(const QString &word); static LibraryTreeItem *deepResolve(LibraryTreeItem *pItem, QStringList nameComponents); QList getCandidateContexts(QStringList nameComponents); diff --git a/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.cpp b/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.cpp index fbb12ad58..29d88a8eb 100644 --- a/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.cpp +++ b/OMEdit/OMEditGUI/Modeling/LibraryTreeWidget.cpp @@ -695,8 +695,8 @@ void LibraryTreeItem::tryToComplete(QList &completionClasses, QLi completionClasses << (CompleterItem(classes[i]->getName(), classes[i]->getHTMLDescription())); } - const QList &components = baseClasses[bc]->getComponentsList(); if (!baseClasses[bc]->isRootItem() && baseClasses[bc]->getLibraryType() == LibraryTreeItem::Modelica) { + const QList &components = baseClasses[bc]->getComponentsList(); for (int i = 0; i < components.size(); ++i) { if (components[i]->getName().startsWith(lastPart)) completionComponents << CompleterItem(components[i]->getName(), components[i]->getHTMLDescription() + QString("
// Inside %1").arg(baseClasses[bc]->mNameStructure)); From a616903c01211b4a6d2199e40695931d8a0a889a Mon Sep 17 00:00:00 2001 From: Anatoly Trosinenko Date: Sun, 24 Mar 2019 13:03:31 +0300 Subject: [PATCH 3/3] Don't show keyword, etc. completions when in annotation. --- OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp b/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp index cd71f5419..a6ddc2e26 100644 --- a/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp +++ b/OMEdit/OMEditGUI/Editors/ModelicaEditor.cpp @@ -73,7 +73,10 @@ void ModelicaEditor::popUpCompleter() QString word = wordUnderCursor(); mpPlainTextEdit->clearCompleter(); - if (!word.contains('.')) { + QList annotations; + getCompletionAnnotations(stringAfterWord("annotation"), annotations); + + if (!word.contains('.') && annotations.empty()) { QStringList keywords = ModelicaHighlighter::getKeywords(); mpPlainTextEdit->insertCompleterKeywords(keywords); QStringList types = ModelicaHighlighter::getTypes(); @@ -93,8 +96,6 @@ void ModelicaEditor::popUpCompleter() mpPlainTextEdit->insertCompleterSymbols(classes, ":/Resources/icons/completerClass.svg"); mpPlainTextEdit->insertCompleterSymbols(components, ":/Resources/icons/completerComponent.svg"); - QList annotations; - getCompletionAnnotations(stringAfterWord("annotation"), annotations); mpPlainTextEdit->insertCompleterSymbols(annotations, ":/Resources/icons/completerAnnotation.svg"); QCompleter *completer = mpPlainTextEdit->completer();