diff --git a/src/SeerEditorManagerWidget.cpp b/src/SeerEditorManagerWidget.cpp index 03457ab2..a6b983c1 100644 --- a/src/SeerEditorManagerWidget.cpp +++ b/src/SeerEditorManagerWidget.cpp @@ -1035,6 +1035,9 @@ SeerEditorWidgetSource* SeerEditorManagerWidget::createEditorWidgetTab (const QS QObject::connect(editorWidget->sourceArea(), &SeerEditorWidgetSourceArea::addMatrixVisualizer, this, &SeerEditorManagerWidget::handleAddMatrixVisualizer); QObject::connect(editorWidget->sourceArea(), &SeerEditorWidgetSourceArea::addStructVisualizer, this, &SeerEditorManagerWidget::handleAddStructVisualizer); QObject::connect(editorWidget, &SeerEditorWidgetSource::addAlternateDirectory, this, &SeerEditorManagerWidget::handleAddAlternateDirectory); + QObject::connect(editorWidget->sourceArea(), &SeerEditorWidgetSourceArea::gdbGotoDefinition, [this] (const QString& identifier) { + emit gdbGotoDefinitionForward(identifier); + }); // Send the Editor widget the command to load the file. ??? Do better than this. editorWidget->sourceArea()->handleText(text); @@ -1097,6 +1100,9 @@ SeerEditorWidgetSource* SeerEditorManagerWidget::createEditorWidgetTab (const QS QObject::connect(editorWidget->sourceArea(), &SeerEditorWidgetSourceArea::addMatrixVisualizer, this, &SeerEditorManagerWidget::handleAddMatrixVisualizer); QObject::connect(editorWidget->sourceArea(), &SeerEditorWidgetSourceArea::addStructVisualizer, this, &SeerEditorManagerWidget::handleAddStructVisualizer); QObject::connect(editorWidget, &SeerEditorWidgetSource::addAlternateDirectory, this, &SeerEditorManagerWidget::handleAddAlternateDirectory); + QObject::connect(editorWidget->sourceArea(), &SeerEditorWidgetSourceArea::gdbGotoDefinition, [this] (const QString& identifier) { + emit gdbGotoDefinitionForward(identifier); + }); // Load the file. editorWidget->sourceArea()->open(fullname, QFileInfo(file).fileName()); diff --git a/src/SeerEditorManagerWidget.h b/src/SeerEditorManagerWidget.h index 67c92ef9..27c98402 100644 --- a/src/SeerEditorManagerWidget.h +++ b/src/SeerEditorManagerWidget.h @@ -134,6 +134,7 @@ class SeerEditorManagerWidget : public QWidget, protected Ui::SeerEditorManagerW void requestSourceAndAssembly (QString address); void showMessage (QString message, int time); void assemblyTabShown (bool shown); + void gdbGotoDefinitionForward (const QString& identifier); private: SeerEditorWidgetSource* currentEditorWidgetTab (); diff --git a/src/SeerEditorWidgetSource.h b/src/SeerEditorWidgetSource.h index b1650ac4..661b784a 100644 --- a/src/SeerEditorWidgetSource.h +++ b/src/SeerEditorWidgetSource.h @@ -140,12 +140,14 @@ class SeerEditorWidgetSourceArea : public SeerPlainTextEdit { void showAlternateBar (bool flag); void showReloadBar (bool flag); void highlighterSettingsChanged (); + void gdbGotoDefinition (const QString& identifier); public slots: void handleText (const QString& text); void handleHighlighterSettingsChanged (); void handleWatchFileModified (const QString& path); void handleBreakpointToolTip (QPoint pos, const QString& text); + void handleGotoDefinition (); protected: void resizeEvent (QResizeEvent* event); @@ -163,6 +165,8 @@ class SeerEditorWidgetSourceArea : public SeerPlainTextEdit { void updateBreakPointArea (const QRect& rect, int dy); private: + bool isValidIdentifier (const QString& text); + QString _fullname; QString _file; QString _alternateDirectory; @@ -194,6 +198,9 @@ class SeerEditorWidgetSourceArea : public SeerPlainTextEdit { int _sourceTabSize; QString _externalEditorCommand; + + bool _ctrlHeld = false; + QString _wordUnderCursor; }; class SeerEditorWidgetSourceLineNumberArea : public QWidget { diff --git a/src/SeerEditorWidgetSourceAreas.cpp b/src/SeerEditorWidgetSourceAreas.cpp index 60571dc4..277ed6dc 100644 --- a/src/SeerEditorWidgetSourceAreas.cpp +++ b/src/SeerEditorWidgetSourceAreas.cpp @@ -69,6 +69,10 @@ SeerEditorWidgetSourceArea::SeerEditorWidgetSourceArea(QWidget* parent) : SeerPl QObject::connect(this, &SeerEditorWidgetSourceArea::updateRequest, this, &SeerEditorWidgetSourceArea::updateBreakPointArea); QObject::connect(this, &SeerEditorWidgetSourceArea::highlighterSettingsChanged, this, &SeerEditorWidgetSourceArea::handleHighlighterSettingsChanged); + // Feature: Go to definition (F12) + QShortcut* gotoDefShortcut = new QShortcut(QKeySequence(Qt::Key_F12), this); + QObject::connect(gotoDefShortcut, &QShortcut::activated, this, &SeerEditorWidgetSourceArea::handleGotoDefinition); + setCurrentLine(0); updateMarginAreasWidth(0); @@ -2025,3 +2029,57 @@ void SeerEditorWidgetSourceBreakPointArea::mouseReleaseEvent (QMouseEvent* event QWidget::mouseReleaseEvent(event); } +/*********************************************************************************************************************** + * Go to definition feature * + **********************************************************************************************************************/ +// Check text and decide if that text is valid identifier (function, variable, type name) +bool SeerEditorWidgetSourceArea::isValidIdentifier(const QString& text) +{ + static const QSet keywords = { + // Add your C and C++ keywords as QString literals here + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", + "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", + "register", "restrict", "return", "short", "signed", "sizeof", "static", "struct", + "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", + "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", + "_Static_assert", "_Thread_local", + + "alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor", "bool", "catch", + "char16_t", "char32_t", "class", "compl", "const_cast", "constexpr", "decltype", + "delete", "dynamic_cast", "explicit", "export", "false", "friend", "mutable", "namespace", + "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", + "protected", "public", "reinterpret_cast", "static_assert", "static_cast", "template", + "this", "thread_local", "throw", "true", "try", "typeid", "typename", "using", + "virtual", "wchar_t", "xor", "xor_eq" + }; + + if (text.isEmpty()) + return false; + + if (keywords.contains(text)) + return false; + + QChar firstChar = text[0]; + if (!firstChar.isLetter() && firstChar != '_') + return false; + + for (int i = 1; i < text.size(); ++i) { + QChar ch = text[i]; + if (!ch.isLetterOrNumber() && ch != '_') + return false; + } + + return true; +} + +// When F12 is pressed, try to look for the word under cursor, if it's a valid identifier then emit gdbGotoDefinition signal +void SeerEditorWidgetSourceArea::handleGotoDefinition() +{ + QTextCursor cursor = textCursor(); + cursor.select(QTextCursor::WordUnderCursor); + QString wordUnderCursor = cursor.selectedText(); + if (isValidIdentifier(wordUnderCursor)) + { + emit gdbGotoDefinition(wordUnderCursor); + } +} diff --git a/src/SeerGdbWidget.cpp b/src/SeerGdbWidget.cpp index 149c98c0..9881cbb1 100644 --- a/src/SeerGdbWidget.cpp +++ b/src/SeerGdbWidget.cpp @@ -135,6 +135,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { QObject::connect(_gdbMonitor, &GdbMonitor::astrixTextOutput, this, &SeerGdbWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::equalTextOutput, this, &SeerGdbWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::tildeTextOutput, this, &SeerGdbWidget::handleText); + QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, this, &SeerGdbWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, threadManagerWidget->threadFramesBrowserWidget(), &SeerThreadFramesBrowserWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::equalTextOutput, threadManagerWidget->threadFramesBrowserWidget(), &SeerThreadFramesBrowserWidget::handleText); @@ -366,6 +367,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { #endif QObject::connect(this, &SeerGdbWidget::stateChanged, editorManagerWidget, &SeerEditorManagerWidget::handleGdbStateChanged); + QObject::connect(editorManagerWidget, &SeerEditorManagerWidget::gdbGotoDefinitionForward, this, &SeerGdbWidget::handleGdbGotoDefinition); // Restore window settings. readSettings(); @@ -796,6 +798,43 @@ void SeerGdbWidget::handleText (const QString& text) { handleGdbSignalListValues("all"); + }else if (text.startsWith("^done,symbols")) // seeking for function, variable and type identifiers + { + //^done,symbols={debug=[{filename=" ",fullname=" ", + // symbols=[{line=" ",name="uwTick",type="volatile uint32_t",description="volatile uint32_t uwTick;"},}]} + _gotoDefMutex.lock(); + + QString debug_text = Seer::parseFirst(text, "debug=", '[', ']', false); + QStringList filenames_list = Seer::parse(debug_text, "", '{', '}', false); + + for (const auto& filename_entry : filenames_list) { + + QString filename_text = Seer::parseFirst(filename_entry, "filename=", '"', '"', false); + QString fullname_text = Seer::parseFirst(filename_entry, "fullname=", '"', '"', false); + + // If that file is not in source browser, skip it + if (sourceLibraryManagerWidget->sourceBrowserWidget()->findFileWithRegrex(fullname_text).isEmpty()) + continue; + + QString symbols_text = Seer::parseFirst(filename_entry, "symbols=", '[', ']', false); + QStringList symbols_list = Seer::parse(symbols_text, "", '{', '}', false); + + for (const auto& symbol_entry : symbols_list) { + + QString line_text = Seer::parseFirst(symbol_entry, "line=", '"', '"', false); + QString name_text = Seer::parseFirst(symbol_entry, "name=", '"', '"', false); + // name_text may be st like: function_name(params...) , so only extract function_name part + name_text = name_text.section('(', 0, 0).trimmed(); + if (name_text == _gotoDefIdentifier) // you found it! signal to open file + { + // editorManagerWidget->setEnableOpenFile(true); // raise this flag to allow opening file + editorManagerWidget->handleOpenFile(filename_text, fullname_text, line_text.toInt()); + } + } + } + + _gotoDefWaitCond.notify_one(); + _gotoDefMutex.unlock(); }else{ // All other text is ignored by this widget. } @@ -3093,6 +3132,28 @@ void SeerGdbWidget::handleAboutToQuit () { setIsQuitting(true); } +void SeerGdbWidget::handleGdbGotoDefinition(const QString& identifier) { + // Raise the flag + _gotoDefIdentifier = identifier; + QApplication::setOverrideCursor(Qt::BusyCursor); + // Create a thread handling this + _workerThread = QThread::create([this]() { + gotoDefinitionWorker(_gotoDefIdentifier); // Run background logic here + }); + QObject::connect(_workerThread, &QThread::finished, _workerThread, &QObject::deleteLater); + _workerThread->start(); +} + +void SeerGdbWidget::gotoDefinitionWorker(const QString& identifier) { + + // For Desktop applications only + syncFindVariableIdentifier(identifier); + syncFindFunctionIdentifier(identifier); + syncFindTypeIdentifier(identifier); + // setSeekIdentifierFlag(false); // Lower the flag + QApplication::restoreOverrideCursor(); +} + void SeerGdbWidget::writeSettings () { //qDebug() << "Write Settings"; @@ -3855,3 +3916,43 @@ void SeerGdbWidget::delay (int seconds) { } } +/*********************************************************************************************************************** + * Functions for handling Gdb and Seer synchronization * + **********************************************************************************************************************/ +// For finding variable, function, and type identifiers in a synchronous manner +void SeerGdbWidget::syncFindVariableIdentifier(const QString& identifier) +{ + _gotoDefMutex.lock(); + emit requestFindVariableIdentifier(identifier); + _gotoDefWaitCond.wait(&_gotoDefMutex); + _gotoDefMutex.unlock(); +} + +void SeerGdbWidget::syncFindFunctionIdentifier (const QString& identifier) +{ + _gotoDefMutex.lock(); + emit requestFindFunctionIdentifier(identifier); + _gotoDefWaitCond.wait(&_gotoDefMutex); + _gotoDefMutex.unlock(); +} + +void SeerGdbWidget::syncFindTypeIdentifier (const QString& identifier) +{ + _gotoDefMutex.lock(); + emit requestFindTypeIdentifier(identifier); + _gotoDefWaitCond.wait(&_gotoDefMutex); + _gotoDefMutex.unlock(); +} + +void SeerGdbWidget::gdbFindVariableIdentifier(const QString& identifier) +{ + handleGdbCommand("-symbol-info-variables --name " + identifier); +} +void SeerGdbWidget::gdbFindFunctionIdentifier (const QString& identifier) +{ + handleGdbCommand("-symbol-info-functions --name " + identifier); +} +void SeerGdbWidget::gdbFindTypeIdentifier (const QString& identifier) +{ + handleGdbCommand("-symbol-info-types --name " + identifier); +} diff --git a/src/SeerGdbWidget.h b/src/SeerGdbWidget.h index 593f9a4e..c84dd14e 100644 --- a/src/SeerGdbWidget.h +++ b/src/SeerGdbWidget.h @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include "ui_SeerGdbWidget.h" @@ -368,6 +371,11 @@ class SeerGdbWidget : public QWidget, protected Ui::SeerGdbWidgetForm { void handleAboutToQuit (); + void handleGdbGotoDefinition (const QString& identifier); + void gdbFindVariableIdentifier (const QString& identifier); + void gdbFindFunctionIdentifier (const QString& identifier); + void gdbFindTypeIdentifier (const QString& identifier); + signals: void stoppingPointReached (); void sessionTerminated (); @@ -376,6 +384,9 @@ class SeerGdbWidget : public QWidget, protected Ui::SeerGdbWidgetForm { void recordSettingsChanged (); void stateChanged (); void gdbCommandLogout (const QString& text); + void requestFindVariableIdentifier (const QString& identifier); + void requestFindFunctionIdentifier (const QString& identifier); + void requestFindTypeIdentifier (const QString& identifier); protected: @@ -389,6 +400,13 @@ class SeerGdbWidget : public QWidget, protected Ui::SeerGdbWidgetForm { void sendGdbInterrupt (int signal); void delay (int seconds); + void gotoDefinitionWorker (const QString& identifier); + + // Functions for Gdb and Seer synchronization + void syncFindVariableIdentifier (const QString& identifier); + void syncFindFunctionIdentifier (const QString& identifier); + void syncFindTypeIdentifier (const QString& identifier); + bool _isQuitting; QString _gdbLauncher; QString _gdbProgram; @@ -440,5 +458,12 @@ class SeerGdbWidget : public QWidget, protected Ui::SeerGdbWidgetForm { QVector _dataExpressionName; QStringList _ignoreFilePatterns; + + QThread* _workerThread; + + // Variable for Go to Definition handling + QString _gotoDefIdentifier; + QMutex _gotoDefMutex; + QWaitCondition _gotoDefWaitCond; }; diff --git a/src/SeerMainWindow.cpp b/src/SeerMainWindow.cpp index 2f65f832..e624866e 100644 --- a/src/SeerMainWindow.cpp +++ b/src/SeerMainWindow.cpp @@ -217,6 +217,11 @@ SeerMainWindow::SeerMainWindow(QWidget* parent) : QMainWindow(parent) { QObject::connect(gdbWidget, &SeerGdbWidget::sessionTerminated, runStatus, &SeerRunStatusIndicator::handleSessionTerminated); handleRecordSettingsChanged(); + // Connect Go to Definition signal/slot. + QObject::connect(gdbWidget, &SeerGdbWidget::requestFindVariableIdentifier, gdbWidget, &SeerGdbWidget::gdbFindVariableIdentifier); + QObject::connect(gdbWidget, &SeerGdbWidget::requestFindFunctionIdentifier, gdbWidget, &SeerGdbWidget::gdbFindFunctionIdentifier); + QObject::connect(gdbWidget, &SeerGdbWidget::requestFindTypeIdentifier, gdbWidget, &SeerGdbWidget::gdbFindTypeIdentifier); + // // Initialize contents. // diff --git a/src/SeerSourceBrowserWidget.cpp b/src/SeerSourceBrowserWidget.cpp index 04ada953..a4143660 100644 --- a/src/SeerSourceBrowserWidget.cpp +++ b/src/SeerSourceBrowserWidget.cpp @@ -119,11 +119,11 @@ void SeerSourceBrowserWidget::handleText (const QString& text) { //qDebug() << file_text << fullname_text; // Skip duplicates - if (files.contains(fullname_text)) { + if (_sourceFiles.contains(fullname_text)) { continue; } - files.insert(fullname_text, file_text); + _sourceFiles.insert(fullname_text, file_text); // Add the file to the tree. QTreeWidgetItem* item = new QTreeWidgetItem; @@ -171,6 +171,7 @@ void SeerSourceBrowserWidget::handleSessionTerminated () { // Delete previous files. deleteChildItems(); + _sourceFiles.clear(); } void SeerSourceBrowserWidget::handleItemDoubleClicked (QTreeWidgetItem* item, int column) { @@ -296,3 +297,16 @@ void SeerSourceBrowserWidget::deleteChildItems () { } } +const QString& SeerSourceBrowserWidget::findFileWithRegrex(const QString& expression) +{ + QMap::const_iterator it; + for (it = _sourceFiles.constBegin(); it != _sourceFiles.constEnd(); ++it) { + if (it.key().contains(expression)) { + return it.key(); + } + } + static const QString empty; + return empty; +} + + diff --git a/src/SeerSourceBrowserWidget.h b/src/SeerSourceBrowserWidget.h index a529c9d4..32c5f6b5 100644 --- a/src/SeerSourceBrowserWidget.h +++ b/src/SeerSourceBrowserWidget.h @@ -29,6 +29,8 @@ class SeerSourceBrowserWidget : public QWidget, protected Ui::SeerSourceBrowserW void setIgnoreFilePatterns (const QStringList& patterns); const QStringList& ignoreFilePatterns () const; + const QString& findFileWithRegrex (const QString& expression); + public slots: void handleText (const QString& text); void handleSessionTerminated (); @@ -54,5 +56,6 @@ class SeerSourceBrowserWidget : public QWidget, protected Ui::SeerSourceBrowserW QStringList _headerFilePatterns; QStringList _miscFilePatterns; QStringList _ignoreFilePatterns; + QMap _sourceFiles; // save for later use };