Skip to content

Commit be8ee53

Browse files
authored
Fix handling of quoted string literals for autocompletion (OpenModelica#14059)
Fixes OpenModelica#11112 and OpenModelica#6317 Added tests for autocompletion
1 parent 1167fe7 commit be8ee53

File tree

10 files changed

+354
-30
lines changed

10 files changed

+354
-30
lines changed

OMEdit/OMEditLIB/Editors/BaseEditor.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,21 @@ bool BaseEditor::isModelicaModelInPackageOneFile()
21352135
mpModelWidget->getLibraryTreeItem()->isModelica());
21362136
}
21372137

2138+
/*!
2139+
* \brief BaseEditor::completerItemsToStringList
2140+
* Converts the CompleterItem list to QStringList using the CompleterItem::mValue.
2141+
* \param items
2142+
* \return
2143+
*/
2144+
QStringList BaseEditor::completerItemsToStringList(const QList<CompleterItem> &items)
2145+
{
2146+
QStringList list;
2147+
for (const CompleterItem &item : items) {
2148+
list.append(item.mValue);
2149+
}
2150+
return list;
2151+
}
2152+
21382153
/*!
21392154
* \brief BaseEditor::initialize
21402155
* Initializes the editor with default values.

OMEdit/OMEditLIB/Editors/BaseEditor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ class BaseEditor : public QWidget
336336
virtual QString wordUnderCursor();
337337
virtual void symbolAtPosition(const QPoint &pos);
338338
bool isModelicaModelInPackageOneFile();
339+
static QStringList completerItemsToStringList(const QList<CompleterItem> &items);
339340
private:
340341
void initialize();
341342
void createActions();

OMEdit/OMEditLIB/Editors/ModelicaEditor.cpp

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void ModelicaEditor::popUpCompleter()
7878
mpPlainTextEdit->clearCompleter();
7979

8080
QList<CompleterItem> annotations;
81-
bool inAnnotation = getCompletionAnnotations(stringAfterWord("annotation"), annotations);
81+
bool inAnnotation = ModelicaEditor::getCompletionAnnotations(stringAfterWord("annotation"), annotations);
8282
mpPlainTextEdit->insertCompleterSymbols(annotations, ":/Resources/icons/completerAnnotation.svg");
8383

8484
bool startsWithUpperCase = !word.isEmpty() && word[0].isUpper();
@@ -97,13 +97,7 @@ void ModelicaEditor::popUpCompleter()
9797
}
9898
if (!inAnnotation) {
9999
QList<CompleterItem> classes, components;
100-
getCompletionSymbols(word, classes, components);
101-
102-
std::sort(classes.begin(), classes.end());
103-
classes.erase(std::unique(classes.begin(), classes.end()), classes.end());
104-
105-
std::sort(components.begin(), components.end());
106-
components.erase(std::unique(components.begin(), components.end()), components.end());
100+
ModelicaEditor::getCompletionSymbols(getModelWidget()->getLibraryTreeItem(), word, classes, components);
107101

108102
mpPlainTextEdit->insertCompleterSymbols(classes, ":/Resources/icons/completerClass.svg");
109103
mpPlainTextEdit->insertCompleterSymbols(components, ":/Resources/icons/completerComponent.svg");
@@ -149,14 +143,20 @@ LibraryTreeItem *ModelicaEditor::deepResolve(LibraryTreeItem *pItem, QStringList
149143
return pCurrentItem;
150144
}
151145

152-
QList<LibraryTreeItem*> ModelicaEditor::getCandidateContexts(QStringList nameComponents)
146+
/*!
147+
* \brief ModelicaEditor::getCandidateContexts
148+
* \param pLibraryTreeItem
149+
* \param nameComponents
150+
* \return
151+
*/
152+
QList<LibraryTreeItem*> ModelicaEditor::getCandidateContexts(LibraryTreeItem *pLibraryTreeItem, QStringList nameComponents)
153153
{
154154
QList<LibraryTreeItem*> result;
155155
QList<LibraryTreeItem*> roots;
156-
LibraryTreeItem *pItem = getModelWidget()->getLibraryTreeItem();
157-
while (pItem) {
158-
roots.append(pItem->getInheritedClassesDeepList());
159-
pItem = pItem->parent();
156+
157+
while (pLibraryTreeItem) {
158+
roots.append(pLibraryTreeItem->getInheritedClassesDeepList());
159+
pLibraryTreeItem = pLibraryTreeItem->parent();
160160
}
161161

162162
for (int i = 0; i < roots.size(); ++i) {
@@ -235,7 +235,14 @@ QString ModelicaEditor::stringAfterWord(const QString &word)
235235
return plainText.mid(index, pos - index);
236236
}
237237

238-
void ModelicaEditor::getCompletionSymbols(QString word, QList<CompleterItem> &classes, QList<CompleterItem> &components)
238+
/*!
239+
* \brief ModelicaEditor::getCompletionSymbols
240+
* \param pLibraryTreeItem
241+
* \param word
242+
* \param classes
243+
* \param components
244+
*/
245+
void ModelicaEditor::getCompletionSymbols(LibraryTreeItem *pLibraryTreeItem, QString word, QList<CompleterItem> &classes, QList<CompleterItem> &components)
239246
{
240247
QStringList nameComponents = word.split('.');
241248
QString lastPart;
@@ -246,11 +253,17 @@ void ModelicaEditor::getCompletionSymbols(QString word, QList<CompleterItem> &cl
246253
lastPart = "";
247254
}
248255

249-
QList<LibraryTreeItem*> contexts = getCandidateContexts(nameComponents);
256+
QList<LibraryTreeItem*> contexts = ModelicaEditor::getCandidateContexts(pLibraryTreeItem, nameComponents);
250257

251258
for (int i = 0; i < contexts.size(); ++i) {
252259
contexts[i]->tryToComplete(classes, components, lastPart);
253260
}
261+
262+
std::sort(classes.begin(), classes.end());
263+
classes.erase(std::unique(classes.begin(), classes.end()), classes.end());
264+
265+
std::sort(components.begin(), components.end());
266+
components.erase(std::unique(components.begin(), components.end()), components.end());
254267
}
255268

256269
/*!
@@ -264,6 +277,7 @@ LibraryTreeItem *ModelicaEditor::getAnnotationCompletionRoot()
264277
for (int i = 0; i < pLibraryRoot->childrenSize(); ++i) {
265278
if (pLibraryRoot->childAt(i)->getName() == "OpenModelica") {
266279
pModelicaReference = pLibraryRoot->childAt(i);
280+
break;
267281
}
268282
}
269283

@@ -281,7 +295,7 @@ LibraryTreeItem *ModelicaEditor::getAnnotationCompletionRoot()
281295
*/
282296
void ModelicaEditor::getCompletionAnnotations(const QStringList &stack, QList<CompleterItem> &annotations)
283297
{
284-
LibraryTreeItem *pReference = getAnnotationCompletionRoot();
298+
LibraryTreeItem *pReference = ModelicaEditor::getAnnotationCompletionRoot();
285299
if (pReference) {
286300
LibraryTreeItem *pAnnotation = deepResolve(pReference, stack);
287301
if (pAnnotation) {
@@ -323,13 +337,16 @@ bool ModelicaEditor::getCompletionAnnotations(const QString &str, QList<Complete
323337

324338
// First, handle string literals
325339
if (ch == '"') {
326-
for (++i; i < str.size() && str[i] != '"'; ++i) {
327-
if (str[i] == '\\')
328-
++i;
340+
++i;
341+
while (i < str.size()) {
342+
if (str[i] == '\\') {
343+
++i; // skip escaped character
344+
} else if (str[i] == '"') {
345+
break; // end of quoted string
346+
}
347+
++i;
329348
}
330-
// skipped, restarting as usual
331-
--i;
332-
continue;
349+
continue; // don't treat quoted string contents as syntax
333350
}
334351

335352
// Now, handle the stack of annotations
@@ -355,7 +372,7 @@ bool ModelicaEditor::getCompletionAnnotations(const QString &str, QList<Complete
355372
return false;
356373
}
357374
stack.pop_front(); // pop 'annotation'
358-
getCompletionAnnotations(stack, annotations);
375+
ModelicaEditor::getCompletionAnnotations(stack, annotations);
359376
return true;
360377
}
361378

OMEdit/OMEditLIB/Editors/ModelicaEditor.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ class ModelicaEditor : public BaseEditor
6262
virtual void symbolAtPosition(const QPoint &pos) override;
6363
QString stringAfterWord(const QString &word);
6464
static LibraryTreeItem *deepResolve(LibraryTreeItem *pItem, QStringList nameComponents);
65-
QList<LibraryTreeItem *> getCandidateContexts(QStringList nameComponents);
65+
static QList<LibraryTreeItem *> getCandidateContexts(LibraryTreeItem *pLibraryTreeItem, QStringList nameComponents);
6666
static void tryToCompleteInSingleContext(QStringList &result, LibraryTreeItem *pItem, QString lastPart);
67-
void getCompletionSymbols(QString word, QList<CompleterItem> &classes, QList<CompleterItem> &components);
68-
LibraryTreeItem *getAnnotationCompletionRoot();
69-
void getCompletionAnnotations(const QStringList &stack, QList<CompleterItem> &annotations);
70-
bool getCompletionAnnotations(const QString &str, QList<CompleterItem> &annotations);
67+
static void getCompletionSymbols(LibraryTreeItem *pLibraryTreeItem, QString word, QList<CompleterItem> &classes, QList<CompleterItem> &components);
68+
static LibraryTreeItem *getAnnotationCompletionRoot();
69+
static void getCompletionAnnotations(const QStringList &stack, QList<CompleterItem> &annotations);
70+
static bool getCompletionAnnotations(const QString &str, QList<CompleterItem> &annotations);
7171
static QList<CompleterItem> getCodeSnippets();
7272
private:
7373
QString mLastValidText;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#
2+
# This file is part of OpenModelica.
3+
#
4+
# Copyright (c) 1998-CurrentYear, Open Source Modelica Consortium (OSMC),
5+
# c/o Linköpings universitet, Department of Computer and Information Science,
6+
# SE-58183 Linköping, Sweden.
7+
#
8+
# All rights reserved.
9+
#
10+
# THIS PROGRAM IS PROVIDED UNDER THE TERMS OF GPL VERSION 3 LICENSE OR
11+
# THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2.
12+
# ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE
13+
# OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3, ACCORDING TO RECIPIENTS CHOICE.
14+
#
15+
# The OpenModelica software and the Open Source Modelica
16+
# Consortium (OSMC) Public License (OSMC-PL) are obtained
17+
# from OSMC, either from the above address,
18+
# from the URLs: http://www.ida.liu.se/projects/OpenModelica or
19+
# http://www.openmodelica.org, and in the OpenModelica distribution.
20+
# GNU version 3 is obtained from: http://www.gnu.org/copyleft/gpl.html.
21+
#
22+
# This program is distributed WITHOUT ANY WARRANTY; without
23+
# even the implied warranty of MERCHANTABILITY or FITNESS
24+
# FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH
25+
# IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL.
26+
#
27+
# See the full OSMC Public License conditions for more details.
28+
#
29+
#/
30+
31+
include(../Common/Testsuite.pri)
32+
include(../Common/Util.pri)
33+
34+
TARGET = AutoCompletion
35+
36+
SOURCES += AutoCompletionTest.cpp
37+
38+
HEADERS += AutoCompletionTest.h
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* This file is part of OpenModelica.
3+
*
4+
* Copyright (c) 1998-CurrentYear, Open Source Modelica Consortium (OSMC),
5+
* c/o Linköpings universitet, Department of Computer and Information Science,
6+
* SE-58183 Linköping, Sweden.
7+
*
8+
* All rights reserved.
9+
*
10+
* THIS PROGRAM IS PROVIDED UNDER THE TERMS OF GPL VERSION 3 LICENSE OR
11+
* THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2.
12+
* ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES
13+
* RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3,
14+
* ACCORDING TO RECIPIENTS CHOICE.
15+
*
16+
* The OpenModelica software and the Open Source Modelica
17+
* Consortium (OSMC) Public License (OSMC-PL) are obtained
18+
* from OSMC, either from the above address,
19+
* from the URLs: http://www.ida.liu.se/projects/OpenModelica or
20+
* http://www.openmodelica.org, and in the OpenModelica distribution.
21+
* GNU version 3 is obtained from: http://www.gnu.org/copyleft/gpl.html.
22+
*
23+
* This program is distributed WITHOUT ANY WARRANTY; without
24+
* even the implied warranty of MERCHANTABILITY or FITNESS
25+
* FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH
26+
* IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL.
27+
*
28+
* See the full OSMC Public License conditions for more details.
29+
*
30+
*/
31+
/*
32+
* @author Adeel Asghar <adeel.asghar@liu.se>
33+
*/
34+
35+
#include "AutoCompletionTest.h"
36+
#include "Util.h"
37+
#include "OMEditApplication.h"
38+
#include "MainWindow.h"
39+
#include "Editors/ModelicaEditor.h"
40+
#include "Modeling/LibraryTreeWidget.h"
41+
42+
#define GC_THREADS
43+
extern "C" {
44+
#include "meta/meta_modelica.h"
45+
}
46+
47+
OMEDITTEST_MAIN(AutoCompletionTest)
48+
49+
void AutoCompletionTest::initTestCase()
50+
{
51+
// Load OpenModelica for auto completion
52+
MainWindow::instance()->getLibraryWidget()->getLibraryTreeModel()->addModelicaLibraries();
53+
54+
MainWindow::instance()->getLibraryWidget()->openFile(QFINDTESTDATA(mFileName));
55+
if (!MainWindow::instance()->getOMCProxy()->existClass(mModelName)) {
56+
QFAIL(QString("Failed to load file %1").arg(mFileName).toStdString().c_str());
57+
}
58+
}
59+
60+
void AutoCompletionTest::inOutAnnotationTest()
61+
{
62+
QFETCH(QString, word);
63+
QFETCH(bool, result);
64+
65+
QList<CompleterItem> annotations;
66+
QCOMPARE(ModelicaEditor::getCompletionAnnotations(word, annotations), result);
67+
}
68+
69+
void AutoCompletionTest::inOutAnnotationTest_data()
70+
{
71+
QTest::addColumn<QString>("word");
72+
QTest::addColumn<bool>("result");
73+
74+
QTest::newRow("InAnnotation")
75+
<< "annotation(Dialog(tab = \"General\")"
76+
<< true;
77+
78+
QTest::newRow("InDialogAnnotation")
79+
<< "annotation(Dialog(tab = \"General\""
80+
<< true;
81+
82+
QTest::newRow("OutDialogAnnotationButInAnnotation")
83+
<< "annotation(Dialog(tab = \"General\")"
84+
<< true;
85+
86+
QTest::newRow("OutAnnotation1")
87+
<< "annotation(Dialog(tab = \"General\"))"
88+
<< false;
89+
90+
QTest::newRow("OutAnnotation2")
91+
<< "annotation(Dialog(tab = \"General\"));"
92+
<< false;
93+
}
94+
95+
void AutoCompletionTest::getCompletionAnnotationsTest()
96+
{
97+
QFETCH(QString, word);
98+
QFETCH(QStringList, result);
99+
100+
QList<CompleterItem> annotations;
101+
ModelicaEditor::getCompletionAnnotations(word, annotations);
102+
QCOMPARE(BaseEditor::completerItemsToStringList(annotations), result);
103+
}
104+
105+
void AutoCompletionTest::getCompletionAnnotationsTest_data()
106+
{
107+
QTest::addColumn<QString>("word");
108+
QTest::addColumn<QStringList>("result");
109+
110+
QTest::newRow("DocumentationAnnotation")
111+
<< "annotation(Documentation("
112+
<< QStringList({"info = ", "revisions = "});
113+
114+
QTest::newRow("ExperimentAnnotation")
115+
<< "annotation(experiment("
116+
<< QStringList({"StartTime = 0", "StopTime = 1", "Interval = 0.002", "Tolerance = 1e-6"});
117+
}
118+
119+
void AutoCompletionTest::getCompletionSymbolsTest()
120+
{
121+
LibraryTreeItem *pLibraryTreeItem = MainWindow::instance()->getLibraryWidget()->getLibraryTreeModel()->findLibraryTreeItem(mModelName);
122+
if (!pLibraryTreeItem) {
123+
QFAIL(QString("Failed to find library tree item for %1").arg(mModelName).toStdString().c_str());
124+
}
125+
126+
QFETCH(QString, word);
127+
QFETCH(QStringList, expectedClasses);
128+
QFETCH(QStringList, expectedComponents);
129+
130+
QList<CompleterItem> classes, components;
131+
ModelicaEditor::getCompletionSymbols(pLibraryTreeItem, word, classes, components);
132+
QCOMPARE(BaseEditor::completerItemsToStringList(classes), expectedClasses);
133+
QCOMPARE(BaseEditor::completerItemsToStringList(components), expectedComponents);
134+
}
135+
136+
void AutoCompletionTest::getCompletionSymbolsTest_data()
137+
{
138+
QTest::addColumn<QString>("word");
139+
QTest::addColumn<QStringList>("expectedClasses");
140+
QTest::addColumn<QStringList>("expectedComponents");
141+
142+
// Test the auto completion for classes and components in the model
143+
QTest::newRow("EmptyWord")
144+
<< ""
145+
<< QStringList({"KindOfController", "test_annotation"})
146+
<< QStringList({"Bonjour", "Hej", "Hello", "Hola", "isActive"});
147+
148+
QTest::newRow("ClassName")
149+
<< "test_annotation."
150+
<< QStringList({"KindOfController"})
151+
<< QStringList({"Bonjour", "Hej", "Hello", "Hola", "isActive"});
152+
}
153+
154+
void AutoCompletionTest::cleanupTestCase()
155+
{
156+
MainWindow::instance()->close();
157+
}

0 commit comments

Comments
 (0)