From 3d103ddee9fcd15aed86e5e353d5f2cd5805c0c9 Mon Sep 17 00:00:00 2001 From: "royqh1979@gmail.com" Date: Sun, 29 Aug 2021 17:23:40 +0800 Subject: [PATCH] work save: hint --- RedPandaIDE/editor.cpp | 230 ++++++++++++++++++++++++++++++- RedPandaIDE/editor.h | 23 ++++ RedPandaIDE/parser/cppparser.cpp | 51 ++++++- RedPandaIDE/parser/cppparser.h | 2 +- RedPandaIDE/qsynedit/SynEdit.cpp | 43 ++++++ RedPandaIDE/qsynedit/SynEdit.h | 5 + 6 files changed, 349 insertions(+), 5 deletions(-) diff --git a/RedPandaIDE/editor.cpp b/RedPandaIDE/editor.cpp index 3aa16326..f3569335 100644 --- a/RedPandaIDE/editor.cpp +++ b/RedPandaIDE/editor.cpp @@ -21,9 +21,11 @@ #include #include #include +#include #include "iconsmanager.h" #include "debugger.h" #include "editorlist.h" +#include using namespace std; @@ -63,7 +65,9 @@ Editor::Editor(QWidget *parent, const QString& filename, mSyntaxWarningColor("orange"), mLineCount(0), mActiveBreakpointLine(-1), - mLastIdCharPressed(0) + mLastIdCharPressed(0), + mCurrentWord(), + mCurrentTipType(TipType::None) { if (mFilename.isEmpty()) { newfileCount++; @@ -575,6 +579,115 @@ void Editor::onPreparePaintHighlightToken(int row, int column, const QString &to } } +bool Editor::event(QEvent *event) +{ + if (event->type() == QEvent::ToolTip) { + QHelpEvent *helpEvent = static_cast(event); + BufferCoord p; + TipType reason = getTipType(helpEvent->pos(),p); + qDebug()<<(int)reason; + PSyntaxIssue pError; + int line ; + if (reason == TipType::Error) { + pError = getSyntaxIssueAtPosition(p); + } else if ((reason == TipType::None) && GetLineOfMouse(line)) { + //it's on gutter + //see if its error; + PSyntaxIssueList issues = getSyntaxIssuesAtLine(line); + if (issues && !issues->isEmpty()) { + reason = TipType::Error; + pError = issues->front(); + } + } + + // Get subject + bool isIncludeLine = false; + BufferCoord pBeginPos,pEndPos; + QString s; + switch (reason) { + case TipType::Preprocessor: + // When hovering above a preprocessor line, determine if we want to show an include or a identifier hint + s = lines()->getString(p.Line - 1); + isIncludeLine = mParser->isIncludeLine(s); + if (!isIncludeLine) + s = WordAtRowCol(p); + break; + case TipType::Identifier: + if (pMainWindow->debugger()->executing()) + s = getWordAtPosition(p, pBeginPos,pEndPos, WordPurpose::wpEvaluation); // debugging + else if (//devEditor.ParserHints and + !mCompletionPopup->isVisible() + && !mHeaderCompletionPopup->isVisible()) + s = getWordAtPosition(p, pBeginPos,pEndPos, WordPurpose::wpInformation); // information during coding + break; + case TipType::Selection: + s = selText(); // when a selection is available, always only use that + break; + case TipType::Error: + s = pError->token; + break; + case TipType::None: + //fText.Cursor := crIBeam; // nope + cancelHint(); + event->ignore(); + return true; + } + + + qDebug()<'') then + s = s.trimmed(); + if ((s == mCurrentWord) && (mCurrentTipType == reason)) { + event->ignore(); + return true; // do NOT remove hint when subject stays the same + } + + // Remove hint + cancelHint(); + mCurrentWord = s; + mCurrentTipType = reason; + + // We are allowed to change the cursor +// if (ssCtrl in Shift) then +// fText.Cursor := crHandPoint +// else +// fText.Cursor := crIBeam; + + // Determine what to do with subject + QString hint = ""; + switch (reason) { + case TipType::Preprocessor: + if (isIncludeLine) { + hint = getFileHint(s); + } else if (//devEditor.ParserHints and + !mCompletionPopup->isVisible() + && !mHeaderCompletionPopup->isVisible()) { + hint = getParserHint(s,p.Line); + } + break; + case TipType::Identifier: + case TipType::Selection: + if (!mCompletionPopup->isVisible() + && !mHeaderCompletionPopup->isVisible()) { + if (pMainWindow->debugger()->executing()) { + hint = getDebugHint(s); + } else { //if devEditor.ParserHints { + hint = getParserHint(s, p.Line); + } + } + break; + case TipType::Error: + hint = getErrorHint(s); + } + if (!hint.isEmpty()) { + QToolTip::showText(helpEvent->globalPos(),hint); + } else + event->ignore(); + } + return SynEdit::event(event); +} + void Editor::copyToClipboard() { if (pSettings->editor().copySizeLimit()) { @@ -766,6 +879,8 @@ Editor::PSyntaxIssueList Editor::getSyntaxIssuesAtLine(int line) Editor::PSyntaxIssue Editor::getSyntaxIssueAtPosition(const BufferCoord &pos) { PSyntaxIssueList lst = getSyntaxIssuesAtLine(pos.Line); + if (!lst) + return PSyntaxIssue(); foreach (const PSyntaxIssue& issue, *lst) { if (issue->startChar<=pos.Char && pos.Char<=issue->endChar) return issue; @@ -1735,6 +1850,119 @@ bool Editor::onHeaderCompletionKeyPressed(QKeyEvent *event) return processed; } +Editor::TipType Editor::getTipType(QPoint point, BufferCoord& pos) +{ + // Only allow in the text area... + if (PointToCharLine(point, pos)) { + if (!pMainWindow->debugger()->executing() + && getSyntaxIssueAtPosition(pos)) { + return TipType::Error; + } + + PSynHighlighterAttribute attr; + QString s; + + // Only allow hand tips in highlighted areas + if (GetHighlighterAttriAtRowCol(pos,s,attr)) { + // Only allow Identifiers, Preprocessor directives, and selection + if (attr) { + if (selAvail()) { + // do not allow when dragging selection + if (IsPointInSelection(pos)) + return TipType::Selection; + } else if (attr->name() == SYNS_AttrAreaAIdentifier) + return TipType::Identifier; + else if (attr->name() == SYNS_AttrPreprocessor) + return TipType::Preprocessor; + } + } + } + return TipType::None; +} + +void Editor::cancelHint() +{ + //MainForm.Debugger.OnEvalReady := nil; + + // disable editor hint + QToolTip::hideText(); + mCurrentWord = ""; + mCurrentTipType = TipType::None; +} + +QString Editor::getFileHint(const QString &s) +{ + QString fileName = mParser->getHeaderFileName(mFilename, s); + if (QFileInfo(fileName).exists()) { + return fileName + " - " + tr("Ctrl+click for more info"); + } + return ""; +} + +QString Editor::getParserHint(const QString &s, int line) +{ + // This piece of code changes the parser database, possibly making hints and code completion invalid... + QString result; + PStatement statement = mParser->findStatementOf(mFilename, s, line); + if (!statement) + return result; + if (statement->kind == StatementKind::skFunction + || statement->kind == StatementKind::skConstructor + || statement->kind == StatementKind::skDestructor) { + PStatement parentScope = statement->parentScope.lock(); + if (parentScope && parentScope->kind == StatementKind::skNamespace) { + PStatementList namespaceStatementsList = + mParser->findNamespace(parentScope->command); + if (namespaceStatementsList) { + foreach (const PStatement& namespaceStatement, *namespaceStatementsList) { + QString hint = getHintForFunction(statement,namespaceStatement, + mFilename,line); + if (!hint.isEmpty()) { + if (!result.isEmpty()) + result += "
"; + result += hint; + } + } + } + } else + result = getHintForFunction(statement, parentScope, + mFilename,line); + } else if (statement->line>0) { + QFileInfo fileInfo(statement->fileName); + result = mParser->prettyPrintStatement(statement) + " - " + + QString(" %1 (%2) ") + .arg(fileInfo.fileName()) + .arg(statement->line) + + tr("Ctrl+click for more info"); + } else { // hard defines + result = mParser->prettyPrintStatement(statement); + } +// Result := StringReplace(Result, '|', #5, [rfReplaceAll]); + return result; +} + +QString Editor::getHintForFunction(const PStatement &statement, const PStatement &scopeStatement, const QString& filename, int line) +{ + QString result; + const StatementMap& children = mParser->statementList().childrenStatements(scopeStatement); + foreach (const PStatement& childStatement, children){ + if (statement->command == childStatement->command + && statement->kind == childStatement->kind) { + if ((line < childStatement->line) && + childStatement->fileName == filename) + continue; + if (!result.isEmpty()) + result += "
"; + result = mParser->prettyPrintStatement(childStatement) + " - " + + QString(" %1 (%2) ") + .arg(filename) + .arg(childStatement->line) + + tr("Ctrl+click for more info"); + } + } + return result; +} + QString Editor::getWordAtPosition(const BufferCoord &p, BufferCoord &pWordBegin, BufferCoord &pWordEnd, WordPurpose purpose) { QString result = ""; diff --git a/RedPandaIDE/editor.h b/RedPandaIDE/editor.h index 91f7f43a..849f708f 100644 --- a/RedPandaIDE/editor.h +++ b/RedPandaIDE/editor.h @@ -61,6 +61,14 @@ public: wpInformation // walk backwards over words, array, functions, parents, forwards over words }; + enum class TipType { + Preprocessor, // cursor hovers above preprocessor line + Identifier, // cursor hovers above identifier + Selection, // cursor hovers above selection + None, // mouseover not allowed + Error //Cursor hovers above error line/item; + }; + struct SyntaxIssue { int col; int endCol; @@ -176,6 +184,15 @@ private: bool onCompletionKeyPressed(QKeyEvent* event); bool onHeaderCompletionKeyPressed(QKeyEvent* event); + TipType getTipType(QPoint point, BufferCoord& pos); + void cancelHint(); + QString getFileHint(const QString& s); + QString getParserHint(const QString& s, int line); + QString getDebugHint(const QString& s); + QString getErrorHint(const QString& s); + QString getHintForFunction(const PStatement& statement, const PStatement& scope, + const QString& filename, int line); + private: static int newfileCount; QByteArray mEncodingOption; // the encoding type set by the user @@ -201,6 +218,8 @@ private: std::shared_ptr mHeaderCompletionPopup; int mLastIdCharPressed; bool mUseCppSyntax; + QString mCurrentWord; + TipType mCurrentTipType; // QWidget interface protected: @@ -221,6 +240,10 @@ protected: // SynEdit interface protected: void onPreparePaintHighlightToken(int row, int column, const QString &token, PSynHighlighterAttribute attr, SynFontStyles &style, QColor &foreground, QColor &background) override; + + // QObject interface +public: + bool event(QEvent *event) override; }; #endif // EDITOR_H diff --git a/RedPandaIDE/parser/cppparser.cpp b/RedPandaIDE/parser/cppparser.cpp index d32ccf4b..1f2e080e 100644 --- a/RedPandaIDE/parser/cppparser.cpp +++ b/RedPandaIDE/parser/cppparser.cpp @@ -3,9 +3,11 @@ #include "../utils.h" #include +#include #include #include #include +#include static QAtomicInt cppParserCount(0); CppParser::CppParser(QObject *parent) : QObject(parent) @@ -811,10 +813,53 @@ void CppParser::unFreeze() mLockCount--; } -QString CppParser::prettyPrintStatement(const PStatement& statement, int line) +QString CppParser::prettyPrintStatement(const PStatement& statement, const QString& filename, int line) { - //TODO: implement it - return "not implemented yet"; + QString result; + if (!statement->hintText.isEmpty()) { + if (statement->kind != StatementKind::skPreprocessor) + result = statement->hintText; + else if (statement->command == "__FILE__") + result = '"'+filename+'"'; + else if (statement->command == "__LINE__") + result = QString("\"%1\"").arg(line); + else if (statement->command == "__DATE__") + result = QString("\"%1\"").arg(QDate::currentDate().toString(Qt::ISODate)); + else if (statement->command == "__TIME__") + result = QString("\"%1\"").arg(QTime::currentTime().toString(Qt::ISODate)); + else + result = statement->hintText; + } else { + switch(statement->kind) { + case StatementKind::skFunction: + case StatementKind::skVariable: + case StatementKind::skParameter: + case StatementKind::skClass: + if (statement->scope!= StatementScope::ssLocal) + result = getScopePrefix(statement); // public + result += statement->type + ' '; // void + result += statement->fullName; // A::B::C::Bar + result += getArgsSuffix(statement); // (int a) + break; + case StatementKind::skNamespace: + result = statement->fullName; // Bar + break; + case StatementKind::skConstructor: + result = getScopePrefix(statement); // public + result += QObject::tr("constructor") + ' '; // constructor + result += statement->type + ' '; // void + result += statement->fullName; // A::B::C::Bar + result += getArgsSuffix(statement); // (int a) + break; + case StatementKind::skDestructor: + result = getScopePrefix(statement); // public + result += QObject::tr("destructor") + ' '; // constructor + result += statement->type + ' '; // void + result += statement->fullName; // A::B::C::Bar + result += getArgsSuffix(statement); // (int a) + break; + } + } } QString CppParser::getFirstTemplateParam(const PStatement& statement, diff --git a/RedPandaIDE/parser/cppparser.h b/RedPandaIDE/parser/cppparser.h index d1494243..eae6ba34 100644 --- a/RedPandaIDE/parser/cppparser.h +++ b/RedPandaIDE/parser/cppparser.h @@ -93,7 +93,7 @@ public: //QString statementKindStr(StatementKind value); //QString statementClassScopeStr(StatementClassScope value); - QString prettyPrintStatement(const PStatement& statement, int line = -1); + QString prettyPrintStatement(const PStatement& statement, const QString& filename, int line = -1); diff --git a/RedPandaIDE/qsynedit/SynEdit.cpp b/RedPandaIDE/qsynedit/SynEdit.cpp index 6d01d4b0..723e046f 100644 --- a/RedPandaIDE/qsynedit/SynEdit.cpp +++ b/RedPandaIDE/qsynedit/SynEdit.cpp @@ -535,6 +535,49 @@ BufferCoord SynEdit::getMatchingBracketEx(BufferCoord APoint) return BufferCoord{0,0}; } +bool SynEdit::GetPositionOfMouse(BufferCoord &aPos) +{ + QPoint point = QCursor::pos(); + point = mapFromGlobal(point); + return PointToCharLine(point,aPos); +} + +bool SynEdit::GetLineOfMouse(int &line) +{ + QPoint point = QCursor::pos(); + point = mapFromGlobal(point); + return PointToLine(point,line); +} + +bool SynEdit::PointToCharLine(const QPoint &point, BufferCoord &coord) +{ + // Make sure it fits within the SynEdit bounds (and on the gutter) + if ((point.x() < gutterWidth() + clientLeft()) + || (point.x()>clientWidth()+clientLeft()) + || (point.y() < clientTop()) + || (point.y() > clientTop()+clientHeight())) { + return false; + } + + coord = displayToBufferPos(pixelsToRowColumn(point.x(),point.y())); + return true; +} + +bool SynEdit::PointToLine(const QPoint &point, int &line) +{ + // Make sure it fits within the SynEdit bounds + if ((point.x() < clientLeft()) + || (point.x()>clientWidth()+clientLeft()) + || (point.y() < clientTop()) + || (point.y() > clientTop()+clientHeight())) { + return false; + } + + BufferCoord coord = displayToBufferPos(pixelsToRowColumn(point.x(),point.y())); + line = coord.Line; + return true; +} + void SynEdit::invalidateGutter() { invalidateGutterLines(-1, -1); diff --git a/RedPandaIDE/qsynedit/SynEdit.h b/RedPandaIDE/qsynedit/SynEdit.h index 6d918f5d..8f994448 100644 --- a/RedPandaIDE/qsynedit/SynEdit.h +++ b/RedPandaIDE/qsynedit/SynEdit.h @@ -250,6 +250,11 @@ public: virtual BufferCoord getMatchingBracket(); virtual BufferCoord getMatchingBracketEx(BufferCoord APoint); + bool GetPositionOfMouse(BufferCoord& aPos); + bool GetLineOfMouse(int& line); + bool PointToCharLine(const QPoint& point, BufferCoord& coord); + bool PointToLine(const QPoint& point, int& line); + // setter && getters int topLine() const;