From 6020323818b9788542077dde27771f9420ba03fc Mon Sep 17 00:00:00 2001 From: "royqh1979@gmail.com" Date: Wed, 25 Aug 2021 08:48:33 +0800 Subject: [PATCH] work save --- RedPandaIDE/editor.cpp | 245 ++++++++++++++++++--- RedPandaIDE/editor.h | 17 ++ RedPandaIDE/qsynedit/SynEdit.h | 2 +- RedPandaIDE/widgets/codecompletionview.cpp | 55 +++-- RedPandaIDE/widgets/codecompletionview.h | 21 +- 5 files changed, 286 insertions(+), 54 deletions(-) diff --git a/RedPandaIDE/editor.cpp b/RedPandaIDE/editor.cpp index 86a5ed02..220f4e0d 100644 --- a/RedPandaIDE/editor.cpp +++ b/RedPandaIDE/editor.cpp @@ -60,7 +60,8 @@ Editor::Editor(QWidget *parent, const QString& filename, mSyntaxErrorColor(QColorConstants::Red), mSyntaxWarningColor("orange"), mLineCount(0), - mActiveBreakpointLine(-1) + mActiveBreakpointLine(-1), + mLastIdCharPressed(0) { if (mFilename.isEmpty()) { newfileCount++; @@ -96,6 +97,8 @@ Editor::Editor(QWidget *parent, const QString& filename, initParser(); } + mCompletionPopup = std::make_shared(); + applySettings(); applyColorScheme(pSettings->editor().colorScheme()); @@ -322,45 +325,117 @@ void Editor::focusOutEvent(QFocusEvent *event) void Editor::keyPressEvent(QKeyEvent *event) { bool handled = false; - if (!readOnly()) { - switch (event->key()) { - case Qt::Key_Delete: - // remove completed character - //fLastIdCharPressed:=0; - undoSymbolCompletion(caretX()); - break;; - case Qt::Key_Backspace: - // remove completed character - //fLastIdCharPressed:=0; - undoSymbolCompletion(caretX()-1); - break;; - default: { - QString t = event->text(); - if (!t.isEmpty()) { - QChar ch = t[0]; - switch (ch.unicode()) { - case '"': - case '\'': - case '(': - case ')': - case '{': - case '}': - case '[': - case ']': - case '<': - case '>': - case '*': - handled = handleSymbolCompletion(ch); + auto action = finally([&,this]{ + if (!handled) { + SynEdit::keyPressEvent(event); + } else { + event->accept(); + } + }); + if (readOnly()) + return; + + switch (event->key()) { + case Qt::Key_Delete: + // remove completed character + mLastIdCharPressed = 0; + undoSymbolCompletion(caretX()); + return; + case Qt::Key_Backspace: + // remove completed character + mLastIdCharPressed = 0; + undoSymbolCompletion(caretX()-1); + return; + } + + QString t = event->text(); + if (t.isEmpty()) + return; + + QChar ch = t[0]; + if (isIdentChar(ch)) { + mLastIdCharPressed++; +// if devCodeCompletion.Enabled and devCodeCompletion.ShowCompletionWhileInput then begin + if (mLastIdCharPressed==1) { + if (mParser->isIncludeLine(lineText())) { + // is a #include line + setSelText(ch); + showHeaderCompletion(false); + handled=true; + return; + } else { + QString lastWord = getPreviousWordAtPositionForSuggestion(caretXY()); + if (!lastWord.isEmpty()) { + if (CppTypeKeywords.contains(lastWord)) { + //last word is a type keyword, this is a var or param define, and dont show suggestion + // if devEditor.UseTabnine then + // ShowTabnineCompletion; + return; + } + PStatement statement = mParser->findStatementOf( + mFilename, + lastWord, + caretY()); + StatementKind kind = mParser->getKindOfStatement(statement); + if (kind == StatementKind::skClass + || kind == StatementKind::skEnumClassType + || kind == StatementKind::skEnumType + || kind == StatementKind::skTypedef) { + //last word is a typedef/class/struct, this is a var or param define, and dont show suggestion + // if devEditor.UseTabnine then + // ShowTabnineCompletion; + return; + } } + setSelText(ch); + showCompletion(false); + handled=true; } } + +// } + } else { + //preprocessor ? + if ((mLastIdCharPressed=0) && (ch=='#') && lineText().isEmpty()) { + // and devCodeCompletion.Enabled and devCodeCompletion.ShowCompletionWhileInput + mLastIdCharPressed++; + setSelText(ch); + showCompletion(false); + handled=true; + return; + } + //javadoc directive? + if ((mLastIdCharPressed=0) && (ch=='#') && + lineText().trimmed().startsWith('*')) { +// and devCodeCompletion.Enabled and devCodeCompletion.ShowCompletionWhileInput + mLastIdCharPressed++; + setSelText(ch); + showCompletion(false); + handled=true; + return; + } + mLastIdCharPressed = 0; + switch (ch.unicode()) { + case '"': + case '\'': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + case '<': + case '>': + case '*': + handled = handleSymbolCompletion(ch); + return; } } - if (!handled) { - SynEdit::keyPressEvent(event); - } else { - event->accept(); - } + + // Spawn code completion window if we are allowed to +// if devCodeCompletion.Enabled then begin + handleCodeCompletion(ch); +// end; } void Editor::onGutterPaint(QPainter &painter, int aLine, int X, int Y) @@ -1068,6 +1143,32 @@ bool Editor::handleGlobalIncludeSkip() return false; } +void Editor::handleCodeCompletion(QChar key) +{ + if (!mCompletionPopup->isEnabled()) + return; + switch(key.unicode()) { + case '.': + showCompletion(false); + break; + case '>': + if ((caretX() > 1) && (lineText().length() >= 1) && + (lineText()[caretX() - 2] == '-')) + showCompletion(false); + break; + case ':': + if ((caretX() > 1) && (lineText().length() >= 1) && + (lineText()[caretX() - 2] == ':')) + showCompletion(false); + break; + case '/': + case '\\': + if (mParser->isIncludeLine(lineText())) { + showHeaderCompletion(false); + } + } +} + void Editor::initParser() { mParser = std::make_shared(); @@ -1193,6 +1294,80 @@ void Editor::reparse() parseFile(mParser,mFilename,mInProject); } +void Editor::showCompletion(bool autoComplete) +{ +// if not devCodeCompletion.Enabled then +// Exit; + if (!mParser->enabled()) + return; + + if (mCompletionPopup->isVisible()) // already in search, don't do it again + return; + + QString word=""; + + QString s; + PSynHighlighterAttribute attr; + bool tokenFinished; + SynHighlighterTokenType tokenType; + BufferCoord pBeginPos, pEndPos; + if (GetHighlighterAttriAtRowCol( + BufferCoord{caretX() - 1, + caretY()}, s, tokenFinished,tokenType, attr)) { + if (tokenType == SynHighlighterTokenType::PreprocessDirective) {//Preprocessor + word = getWordAtPosition(this, caretXY(),pBeginPos,pEndPos, wpDirective); +if not StartsStr('#',word) then begin + ShowTabnineCompletion; + Exit; +end; + end else if attr = dmMain.Cpp.CommentAttri then begin //Comment, javadoc tag + word:=GetWordAtPosition(fText, fText.CaretXY,pBeginPos,pEndPos, wpJavadoc); + if not StartsStr('@',word) then begin + Exit; + end; + end else if (attr <> fText.Highlighter.SymbolAttribute) and + (attr <> fText.Highlighter.WhitespaceAttribute) and + (attr <> fText.Highlighter.IdentifierAttribute) then begin + Exit; + end; + } + + // Position it at the top of the next line + P := fText.RowColumnToPixels(fText.DisplayXY); + Inc(P.Y, fText.LineHeight + 2); + fCompletionBox.Position := fText.ClientToScreen(P); + + fCompletionBox.RecordUsage := devCodeCompletion.RecordUsage; + fCompletionBox.SortByScope := devCodeCompletion.SortByScope; + fCompletionBox.ShowKeywords := devCodeCompletion.ShowKeywords; + fCompletionBox.ShowCodeIns := devCodeCompletion.ShowCodeIns; + fCompletionBox.IgnoreCase := devCodeCompletion.IgnoreCase; + fCompletionBox.CodeInsList := dmMain.CodeInserts.ItemList; + fCompletionBox.SymbolUsage := dmMain.SymbolUsage; + fCompletionBox.ShowCount := devCodeCompletion.MaxCount; + //Set Font size; + fCompletionBox.FontSize := fText.Font.Size; + + // Redirect key presses to completion box if applicable + fCompletionBox.OnKeyPress := CompletionKeyPress; + fCompletionBox.OnKeyDown := CompletionKeyDown; + fCompletionBox.Parser := fParser; + fCompletionBox.useCppKeyword := fUseCppSyntax; + fCompletionBox.Show; + + // Scan the current function body + fCompletionBox.CurrentStatement := fParser.FindAndScanBlockAt(fFileName, fText.CaretY); + + if word='' then + word:=GetWordAtPosition(fText, fText.CaretXY,pBeginPos,pEndPos, wpCompletion); + //if not fCompletionBox.Visible then + fCompletionBox.PrepareSearch(word, fFileName,pBeginPos.Line); + + // Filter the whole statement list + if fCompletionBox.Search(word, fFileName, autoComplete) then //only one suggestion and it's not input while typing + CompletionInsert(); // if only have one suggestion, just use it +} + const PCppParser &Editor::parser() const { return mParser; diff --git a/RedPandaIDE/editor.h b/RedPandaIDE/editor.h index 338929f8..b3c03373 100644 --- a/RedPandaIDE/editor.h +++ b/RedPandaIDE/editor.h @@ -8,6 +8,7 @@ #include "colorscheme.h" #include "common.h" #include "parser/cppparser.h" +#include "widgets/codecompletionview.h" class SaveException: public std::exception { @@ -48,6 +49,16 @@ public: RawStringNoEscape }; + enum class WordPurpose { + wpCompletion, // walk backwards over words, array, functions, parents, no forward movement + wpEvaluation, // walk backwards over words, array, functions, parents, forwards over words, array + wpHeaderCompletion, // walk backwards over path + wpHeaderCompletionStart, // walk backwards over path, including start '<' or '"' + wpDirective, // preprocessor + wpJavadoc, //javadoc + wpInformation // walk backwards over words, array, functions, parents, forwards over words + }; + struct SyntaxIssue { int col; int endCol; @@ -139,11 +150,15 @@ private: bool handleDoubleQuoteCompletion(); bool handleGlobalIncludeCompletion(); bool handleGlobalIncludeSkip(); + + void handleCodeCompletion(QChar key); void initParser(); void undoSymbolCompletion(int pos); QuoteStatus getQuoteStatus(); void reparse(); + void showCompletion(bool autoComplete); + private: static int newfileCount; QByteArray mEncodingOption; // the encoding type set by the user @@ -165,6 +180,8 @@ private: QSet mBreakpointLines; int mActiveBreakpointLine; PCppParser mParser; + std::shared_ptr mCompletionPopup; + int mLastIdCharPressed; // QWidget interface protected: diff --git a/RedPandaIDE/qsynedit/SynEdit.h b/RedPandaIDE/qsynedit/SynEdit.h index e552c56d..cb9afbbd 100644 --- a/RedPandaIDE/qsynedit/SynEdit.h +++ b/RedPandaIDE/qsynedit/SynEdit.h @@ -353,6 +353,7 @@ signals: void tabSizeChanged(); protected: + bool isIdentChar(const QChar& ch); protected: virtual bool onGetSpecialLineColors(int Line, @@ -366,7 +367,6 @@ protected: virtual void ExecuteCommand(SynEditorCommand Command, QChar AChar, void * pData); private: - bool isIdentChar(const QChar& ch); void clearAreaList(SynEditingAreaList areaList); void computeCaret(int X, int Y); void computeScroll(int X, int Y); diff --git a/RedPandaIDE/widgets/codecompletionview.cpp b/RedPandaIDE/widgets/codecompletionview.cpp index 5a220084..7065ce87 100644 --- a/RedPandaIDE/widgets/codecompletionview.cpp +++ b/RedPandaIDE/widgets/codecompletionview.cpp @@ -9,6 +9,8 @@ CodeCompletionView::CodeCompletionView(QWidget *parent) : { setWindowFlags(Qt::Popup); mListView = new CodeCompletionListView(this); + mModel=new CodeCompletionListModel(&mCompletionStatementList); + mListView->setModel(mModel); setLayout(new QVBoxLayout()); layout()->addWidget(mListView); layout()->setMargin(0); @@ -16,17 +18,18 @@ CodeCompletionView::CodeCompletionView(QWidget *parent) : mShowKeywords=true; mUseCppKeyword=true; - mEnabled = true; mOnlyGlobals = false; mShowCount = 1000; mShowCodeIns = true; mIgnoreCase = false; + } CodeCompletionView::~CodeCompletionView() { - + delete mListView; + delete mModel; } void CodeCompletionView::setKeypressedCallback(const KeyPressedCallback &newKeypressedCallback) @@ -37,7 +40,7 @@ void CodeCompletionView::setKeypressedCallback(const KeyPressedCallback &newKeyp void CodeCompletionView::prepareSearch(const QString &phrase, const QString &filename, int line) { QMutexLocker locker(&mMutex); - if (!mEnabled) + if (!isEnabled()) return; mPhrase = phrase; //Screen.Cursor := crHourglass; @@ -55,10 +58,9 @@ void CodeCompletionView::prepareSearch(const QString &phrase, const QString &fil setCursor(oldCursor); } -bool CodeCompletionView::search(const QString &phrase, const QString &filename, bool autoHideOnSingleResult) +bool CodeCompletionView::search(const QString &phrase, bool autoHideOnSingleResult) { QMutexLocker locker(&mMutex); - bool result = false; mPhrase = phrase; @@ -66,7 +68,7 @@ bool CodeCompletionView::search(const QString &phrase, const QString &filename, hide(); return false; } - if (!mEnabled) { + if (!isEnabled()) { hide(); return false; } @@ -86,12 +88,10 @@ bool CodeCompletionView::search(const QString &phrase, const QString &filename, QString symbol = phrase.mid(i); // filter fFullCompletionStatementList to fCompletionStatementList filterList(symbol); - + mModel->notifyUpdated(); + setCursor(oldCursor); if (!mCompletionStatementList.isEmpty()) { - //todo:update model - - setCursor(oldCursor); // if only one suggestion, and is exactly the symbol to search, hide the frame (the search is over) // if only one suggestion and auto hide , don't show the frame if(mCompletionStatementList.count() == 1) @@ -100,10 +100,9 @@ bool CodeCompletionView::search(const QString &phrase, const QString &filename, return true; } } else { - //todo:update(clear) the model - setCursor(oldCursor); hide(); } + return false; } void CodeCompletionView::addChildren(PStatement scopeStatement, const QString &fileName, int line) @@ -673,6 +672,7 @@ bool CodeCompletionView::isIncluded(const QString &fileName) void CodeCompletionView::hideEvent(QHideEvent *event) { QMutexLocker locker(&mMutex); + mListView->setKeypressedCallback(nullptr); mCompletionStatementList.clear(); mFullCompletionStatementList.clear(); mIncludedFiles.clear(); @@ -688,7 +688,7 @@ CodeCompletionListView::CodeCompletionListView(QWidget *parent) : QListView(pare void CodeCompletionListView::keyPressEvent(QKeyEvent *event) { - if (!mKeypressedCallback(event)) { + if (!mKeypressedCallback || !mKeypressedCallback(event)) { QListView::keyPressEvent(event); } } @@ -702,3 +702,32 @@ void CodeCompletionListView::setKeypressedCallback(const KeyPressedCallback &new { mKeypressedCallback = newKeypressedCallback; } + +CodeCompletionListModel::CodeCompletionListModel(StatementList *statements, QObject *parent): + QAbstractListModel(parent), + mStatements(statements) +{ + +} + +int CodeCompletionListModel::rowCount(const QModelIndex &parent) const +{ + return mStatements->count(); +} + +QVariant CodeCompletionListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (role == Qt::DisplayRole) { + PStatement statement = mStatements->at(index.row()); + return statement->command; + } + return QVariant(); +} + +void CodeCompletionListModel::notifyUpdated() +{ + beginResetModel(); + endResetModel(); +} diff --git a/RedPandaIDE/widgets/codecompletionview.h b/RedPandaIDE/widgets/codecompletionview.h index d03c2f52..aac7937a 100644 --- a/RedPandaIDE/widgets/codecompletionview.h +++ b/RedPandaIDE/widgets/codecompletionview.h @@ -22,6 +22,18 @@ private: KeyPressedCallback mKeypressedCallback; }; +class CodeCompletionListModel : public QAbstractListModel { + Q_OBJECT +public: + explicit CodeCompletionListModel(StatementList* statements,QObject *parent = nullptr); + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + void notifyUpdated(); +private: + const StatementList* mStatements; + +}; + class CodeCompletionView : public QWidget { Q_OBJECT @@ -32,8 +44,7 @@ public: void setKeypressedCallback(const KeyPressedCallback &newKeypressedCallback); void prepareSearch(const QString& phrase, const QString& filename, int line); - bool search(const QString& phrase, const QString& filename, - bool autoHideOnSingleResult); + bool search(const QString& phrase, bool autoHideOnSingleResult); private: @@ -45,12 +56,12 @@ private: bool isIncluded(const QString& fileName); private: CodeCompletionListView * mListView; + CodeCompletionListModel* mModel; QList mCodeInsList; //(Code template list) //QList mCodeInsStatements; //temporary (user code template) statements created when show code suggestion PCppParser mParser; - QList mFullCompletionStatementList; - QList mCompletionStatementList; - bool mEnabled; + StatementList mFullCompletionStatementList; + StatementList mCompletionStatementList; int mShowCount; bool mOnlyGlobals; PStatement mCurrentStatement;