diff --git a/NEWS.md b/NEWS.md index 0b2ea371..ebe84c8c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,7 @@ Red Panda C++ Version 0.13.4 - enhancement: when there are tips showing, don't show mouse tips - enhancement: setting non-ascii font for editors - enhancement: correct handle windows dpi change event + - enhancement: code completion find words with char in the middle Red Panda C++ Version 0.13.3 - enhancement: restore editor position after rename symbol diff --git a/RedPandaIDE/editor.cpp b/RedPandaIDE/editor.cpp index b28c87cb..48a1c186 100644 --- a/RedPandaIDE/editor.cpp +++ b/RedPandaIDE/editor.cpp @@ -2693,7 +2693,6 @@ void Editor::showCompletion(const QString& preWord,bool autoComplete) // fCompletionBox.ShowCount := devCodeCompletion.MaxCount; //Set Font size; mCompletionPopup->setFont(font()); - // Redirect key presses to completion box if applicable //todo: mCompletionPopup->setKeypressedCallback([this](QKeyEvent *event)->bool{ diff --git a/RedPandaIDE/parser/parserutils.h b/RedPandaIDE/parser/parserutils.h index 8a694778..2ffddd31 100644 --- a/RedPandaIDE/parser/parserutils.h +++ b/RedPandaIDE/parser/parserutils.h @@ -125,6 +125,14 @@ enum class EvalStatementKind { using PRemovedStatement = std::shared_ptr; +struct StatementMatchPosition{ + int start; + int end; +}; + +using PStatementMathPosition = std::shared_ptr; + + struct Statement; using PStatement = std::shared_ptr; using StatementList = QList; @@ -157,10 +165,14 @@ struct Statement { bool isInherited; // inherted member; QString fullName; // fullname(including class and namespace), ClassA::foo QSet usingList; // using namespaces - int usageCount; //Usage Count, used by TCodeCompletion - int freqTop; // Usage Count Rank, used by TCodeCompletion - bool caseMatch; // if match with case, used by TCodeCompletion QString noNameArgs;// Args without name + // fields for code completion + int usageCount; //Usage Count + int freqTop; // Usage Count Rank + int matchPosTotal; // total of matched positions + int firstMatchLength; // length of first match; + int caseMatched; // if match with case + QList matchPositions; }; struct EvalStatement; diff --git a/RedPandaIDE/widgets/codecompletionlistview.cpp b/RedPandaIDE/widgets/codecompletionlistview.cpp index 0c6c0f92..db56e165 100644 --- a/RedPandaIDE/widgets/codecompletionlistview.cpp +++ b/RedPandaIDE/widgets/codecompletionlistview.cpp @@ -19,8 +19,13 @@ #include "../editor.h" #include "../editorlist.h" +#include +#include +#include + CodeCompletionListView::CodeCompletionListView(QWidget *parent) : QListView(parent) { + setItemDelegate(&mDelegate); } void CodeCompletionListView::keyPressEvent(QKeyEvent *event) @@ -58,3 +63,53 @@ void CodeCompletionListView::setKeypressedCallback(const KeyPressedCallback &new mKeypressedCallback = newKeypressedCallback; } + +void CodeCompletionListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QVariant data = index.data(); + if (data.canConvert()) { + painter->save(); + QString richText = qvariant_cast(data); + + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + + QColor color = index.data(Qt::ForegroundRole).value(); + if (!color.isValid()) { + color = option.palette.color(QPalette::Text); + } + painter->setPen(color); + QTextDocument doc; + doc.setHtml(richText); + doc.setDefaultFont(painter->font()); + QTransform transform; + transform.translate(option.rect.left(),option.rect.top()); + painter->setTransform(transform); + QRect clipRect = option.rect; + clipRect.moveTopLeft(QPoint(0,0)); + painter->setClipRect(clipRect); + QAbstractTextDocumentLayout::PaintContext ctx; + + ctx.palette.setColor(QPalette::Text, color); + ctx.clip = clipRect; + doc.documentLayout()->draw(painter,ctx); + painter->restore(); + } else { + QStyledItemDelegate::paint(painter, option, index); + } +} + +QSize CodeCompletionListItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QVariant data = index.data(); + if (data.canConvert()) { + QString richText = qvariant_cast(data); + + QTextDocument doc; + doc.setHtml(richText); + return QSize(doc.size().width(),doc.size().height()); + } else { + return QStyledItemDelegate::sizeHint(option, index); + } + +} diff --git a/RedPandaIDE/widgets/codecompletionlistview.h b/RedPandaIDE/widgets/codecompletionlistview.h index 8d604fa3..87ab030b 100644 --- a/RedPandaIDE/widgets/codecompletionlistview.h +++ b/RedPandaIDE/widgets/codecompletionlistview.h @@ -19,10 +19,21 @@ #include #include +#include #include "../parser/parserutils.h" using KeyPressedCallback = std::function; using InputMethodCallback = std::function; +class CodeCompletionListItemDelegate: public QStyledItemDelegate { + Q_OBJECT +public: + + // QAbstractItemDelegate interface +public: + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + class CodeCompletionListView: public QListView { Q_OBJECT public: @@ -39,6 +50,7 @@ protected: void keyPressEvent(QKeyEvent *event) override; private: KeyPressedCallback mKeypressedCallback; + CodeCompletionListItemDelegate mDelegate; // QWidget interface protected: diff --git a/RedPandaIDE/widgets/codecompletionpopup.cpp b/RedPandaIDE/widgets/codecompletionpopup.cpp index b3a7560b..a427ef1a 100644 --- a/RedPandaIDE/widgets/codecompletionpopup.cpp +++ b/RedPandaIDE/widgets/codecompletionpopup.cpp @@ -216,9 +216,19 @@ static bool nameComparator(PStatement statement1,PStatement statement2) { } static bool defaultComparator(PStatement statement1,PStatement statement2) { - if (statement1->caseMatch && !statement2->caseMatch) { + if (statement1->firstMatchLength > statement2->firstMatchLength) { return true; - } else if (!statement1->caseMatch && statement2->caseMatch) { + } else if (statement1->firstMatchLength < statement2->firstMatchLength) { + return false; + } + if (statement1->matchPosTotal < statement2->matchPosTotal) { + return true; + } else if (statement1->matchPosTotal > statement2->matchPosTotal) { + return false; + } + if (statement1->caseMatched > statement2->caseMatched) { + return true; + } else if (statement1->caseMatched < statement2->caseMatched) { return false; } // Show user template first @@ -241,9 +251,19 @@ static bool defaultComparator(PStatement statement1,PStatement statement2) { } static bool sortByScopeComparator(PStatement statement1,PStatement statement2){ - if (statement1->caseMatch && !statement2->caseMatch) { + if (statement1->firstMatchLength > statement2->firstMatchLength) { return true; - } else if (!statement1->caseMatch && statement2->caseMatch) { + } else if (statement1->firstMatchLength < statement2->firstMatchLength) { + return false; + } + if (statement1->matchPosTotal < statement2->matchPosTotal) { + return true; + } else if (statement1->matchPosTotal > statement2->matchPosTotal) { + return false; + } + if (statement1->caseMatched > statement2->caseMatched) { + return true; + } else if (statement1->caseMatched < statement2->caseMatched) { return false; } // Show user template first @@ -279,9 +299,19 @@ static bool sortByScopeComparator(PStatement statement1,PStatement statement2){ } static bool sortWithUsageComparator(PStatement statement1,PStatement statement2) { - if (statement1->caseMatch && !statement2->caseMatch) { + if (statement1->firstMatchLength > statement2->firstMatchLength) { return true; - } else if (!statement1->caseMatch && statement2->caseMatch) { + } else if (statement1->firstMatchLength < statement2->firstMatchLength) { + return false; + } + if (statement1->matchPosTotal < statement2->matchPosTotal) { + return true; + } else if (statement1->matchPosTotal > statement2->matchPosTotal) { + return false; + } + if (statement1->caseMatched > statement2->caseMatched) { + return true; + } else if (statement1->caseMatched < statement2->caseMatched) { return false; } // Show user template first @@ -309,9 +339,19 @@ static bool sortWithUsageComparator(PStatement statement1,PStatement statement2) } static bool sortByScopeWithUsageComparator(PStatement statement1,PStatement statement2){ - if (statement1->caseMatch && !statement2->caseMatch) { + if (statement1->firstMatchLength > statement2->firstMatchLength) { return true; - } else if (!statement1->caseMatch && statement2->caseMatch) { + } else if (statement1->firstMatchLength < statement2->firstMatchLength) { + return false; + } + if (statement1->matchPosTotal < statement2->matchPosTotal) { + return true; + } else if (statement1->matchPosTotal > statement2->matchPosTotal) { + return false; + } + if (statement1->caseMatched > statement2->caseMatched) { + return true; + } else if (statement1->caseMatched < statement2->caseMatched) { return false; } // Show user template first @@ -372,25 +412,66 @@ void CodeCompletionPopup::filterList(const QString &member) // } mCompletionStatementList.clear(); - if (!member.isEmpty()) { // filter - mCompletionStatementList.reserve(mFullCompletionStatementList.size()); - foreach (const PStatement& statement, mFullCompletionStatementList) { - Qt::CaseSensitivity cs = (mIgnoreCase? - Qt::CaseInsensitive: - Qt::CaseSensitive); - if (statement->command.startsWith(member, cs)) { - if (mIgnoreCase) { - statement->caseMatch = - statement->command.startsWith( - member,Qt::CaseSensitive); - } else { - statement->caseMatch = true; - } - mCompletionStatementList.append(statement); + mCompletionStatementList.reserve(mFullCompletionStatementList.size()); + foreach (const PStatement& statement, mFullCompletionStatementList) { + Qt::CaseSensitivity cs = (mIgnoreCase? + Qt::CaseInsensitive: + Qt::CaseSensitive); + + int matched = 0; + int caseMatched = 0; + QString command = statement->command; + int pos = 0; + int lastPos = -10; + int totalPos = 0; + statement->matchPositions.clear(); + foreach (const QChar& ch, member) { + if (mIgnoreCase) + pos = command.indexOf(ch,pos,Qt::CaseInsensitive); + else + pos = command.indexOf(ch,pos,Qt::CaseSensitive); + if (pos<0) { + break; } + if (pos == lastPos+1) { + statement->matchPositions.last()->end++; + } else { + PStatementMathPosition matchPosition=std::make_shared(); + matchPosition->start = pos; + matchPosition->end = pos+1; + statement->matchPositions.append(matchPosition); + } + if (ch==command[pos]) + caseMatched++; + matched++; + totalPos += pos; + lastPos = pos; + pos+=1; } - } else - mCompletionStatementList.append(mFullCompletionStatementList); + + if (mIgnoreCase && matched==member.length()) { + statement->caseMatched = caseMatched; + statement->matchPosTotal = totalPos; + if (member.length()>0) + statement->firstMatchLength = statement->matchPositions.front()->end - statement->matchPositions.front()->start; + else + statement->firstMatchLength = 0; + mCompletionStatementList.append(statement); + } else if (caseMatched == member.length()) { + statement->caseMatched = caseMatched; + statement->matchPosTotal = totalPos; + if (member.length()>0) + statement->firstMatchLength = statement->matchPositions.front()->end - statement->matchPositions.front()->start; + else + statement->firstMatchLength = 0; + mCompletionStatementList.append(statement); + } else { + statement->matchPositions.clear(); + statement->caseMatched = 0; + statement->matchPosTotal = 0; + statement->firstMatchLength = 0; + } + } if (mRecordUsage) { int topCount = 0; int secondCount = 0; @@ -959,7 +1040,12 @@ QVariant CodeCompletionListModel::data(const QModelIndex &index, int role) const switch(role) { case Qt::DisplayRole: { PStatement statement = mStatements->at(index.row()); - return statement->command; + QString text = statement->command; + for (int i = statement->matchPositions.size()-1;i>=0;i--) { + text.insert(statement->matchPositions[i]->end,""); + text.insert(statement->matchPositions[i]->start,""); + } + return text; } case Qt::ForegroundRole: { PStatement statement = mStatements->at(index.row());