diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8c7269..458c6ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,7 @@ set(HEADER_FILES SeerWatchpointsBrowserWidget.h SeerSourceHighlighter.h SeerAdaSourceHighlighter.h + SeerFortranSourceHighlighter.h SeerCppSourceHighlighter.h SeerOdinSourceHighlighter.h SeerRustSourceHighlighter.h @@ -270,6 +271,7 @@ set(SOURCE_FILES SeerWatchpointsBrowserWidget.cpp SeerSourceHighlighter.cpp SeerAdaSourceHighlighter.cpp + SeerFortranSourceHighlighter.cpp SeerCppSourceHighlighter.cpp SeerOdinSourceHighlighter.cpp SeerRustSourceHighlighter.cpp diff --git a/src/SeerEditorConfigPage.cpp b/src/SeerEditorConfigPage.cpp index 3fe37c4..088c946 100644 --- a/src/SeerEditorConfigPage.cpp +++ b/src/SeerEditorConfigPage.cpp @@ -31,6 +31,7 @@ SeerEditorConfigPage::SeerEditorConfigPage(QWidget* parent) : QWidget(parent) { QObject::connect(rustSuffixesLineEdit, &QHistoryLineEdit::lostFocus, this, &SeerEditorConfigPage::handleHighlighterChanged); QObject::connect(odinSuffixesLineEdit, &QHistoryLineEdit::lostFocus, this, &SeerEditorConfigPage::handleHighlighterChanged); QObject::connect(adaSuffixesLineEdit, &QHistoryLineEdit::lostFocus, this, &SeerEditorConfigPage::handleHighlighterChanged); + QObject::connect(fortranSuffixesLineEdit, &QHistoryLineEdit::lostFocus, this, &SeerEditorConfigPage::handleHighlighterChanged); QObject::connect(languageTabWidget, &QTabWidget::currentChanged, this, &SeerEditorConfigPage::handleLanguageTabChanged); QObject::connect(themeApplyToolButton, &QToolButton::clicked, this, &SeerEditorConfigPage::handleApplyTheme); @@ -149,6 +150,8 @@ void SeerEditorConfigPage::setHighlighterSettings (const SeerHighlighterSettings odinSuffixesLineEdit->setCursorPosition(0); adaSuffixesLineEdit->setText(_highlighterSettings.adaSourceSuffixes()); adaSuffixesLineEdit->setCursorPosition(0); + fortranSuffixesLineEdit->setText(_highlighterSettings.fortranSourceSuffixes()); + fortranSuffixesLineEdit->setCursorPosition(0); // Update our sample editor. editorWidget->sourceArea()->setHighlighterSettings(highlighterSettings()); @@ -333,6 +336,7 @@ void SeerEditorConfigPage::handleHighlighterChanged () { languageSettings.setRustSourceSuffixes(rustSuffixesLineEdit->text()); languageSettings.setOdinSourceSuffixes(odinSuffixesLineEdit->text()); languageSettings.setAdaSourceSuffixes(adaSuffixesLineEdit->text()); + languageSettings.setFortranSourceSuffixes(fortranSuffixesLineEdit->text()); // Update our view. setHighlighterSettings(languageSettings); diff --git a/src/SeerEditorConfigPage.ui b/src/SeerEditorConfigPage.ui index 5f6d08d..a32e63e 100644 --- a/src/SeerEditorConfigPage.ui +++ b/src/SeerEditorConfigPage.ui @@ -272,6 +272,33 @@ + + + Fortran + + + + + + Suffixes: + + + + + + + Fortran source suffixes. Separated by '|'. + + + Fortran source suffixes. + + + true + + + + + diff --git a/src/SeerFortranSourceHighlighter.cpp b/src/SeerFortranSourceHighlighter.cpp new file mode 100644 index 0000000..68a28c8 --- /dev/null +++ b/src/SeerFortranSourceHighlighter.cpp @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2026 Ernie Pasveer +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SeerFortranSourceHighlighter.h" + +SeerFortranSourceHighlighter::SeerFortranSourceHighlighter(QTextDocument *parent) : SeerSourceHighlighter(parent) { + + // Set to default formats. + setHighlighterSettings(SeerHighlighterSettings::populate("")); +} + +void SeerFortranSourceHighlighter::setHighlighterSettings(const SeerHighlighterSettings &settings) { + + _highlighterSettings = settings; + + _classFormat = _highlighterSettings.get("Class"); + _quotationFormat = _highlighterSettings.get("Quotation"); + _functionFormat = _highlighterSettings.get("Function"); + _singleLineCommentFormat = _highlighterSettings.get("Comment"); + _multiLineCommentFormat = _highlighterSettings.get("Multiline Comment"); + _keywordFormat = _highlighterSettings.get("Keyword"); + + // Free-form Fortran (90 and later). + const QString keywordPatterns[] = { + QStringLiteral("\\babstract\\b"), QStringLiteral("\\ballocatable\\b"), QStringLiteral("\\ballocate\\b"), + QStringLiteral("\\bassignment\\b"), QStringLiteral("\\bassociate\\b"), QStringLiteral("\\basynchronous\\b"), + QStringLiteral("\\bbackspace\\b"), QStringLiteral("\\bbind\\b"), QStringLiteral("\\bblock\\b"), + QStringLiteral("\\bcall\\b"), QStringLiteral("\\bcase\\b"), QStringLiteral("\\bcharacter\\b"), + QStringLiteral("\\bclass\\b"), QStringLiteral("\\bclose\\b"), QStringLiteral("\\bcommon\\b"), + QStringLiteral("\\bcomplex\\b"), QStringLiteral("\\bconcurrent\\b"), QStringLiteral("\\bcontains\\b"), + QStringLiteral("\\bcontinue\\b"), QStringLiteral("\\bcritical\\b"), QStringLiteral("\\bcycle\\b"), + QStringLiteral("\\bdata\\b"), QStringLiteral("\\bdeallocate\\b"), QStringLiteral("\\bdefault\\b"), + QStringLiteral("\\bdimension\\b"), QStringLiteral("\\bdo\\b"), QStringLiteral("\\bdouble\\b"), + QStringLiteral("\\belemental\\b"), QStringLiteral("\\belse\\b"), QStringLiteral("\\belseif\\b"), + QStringLiteral("\\belsewhere\\b"), QStringLiteral("\\bend\\b"), QStringLiteral("\\benddo\\b"), + QStringLiteral("\\bendfile\\b"), QStringLiteral("\\bendif\\b"), QStringLiteral("\\bentry\\b"), + QStringLiteral("\\benum\\b"), QStringLiteral("\\benumerator\\b"), QStringLiteral("\\bequivalence\\b"), + QStringLiteral("\\berror\\b"), QStringLiteral("\\bexit\\b"), QStringLiteral("\\bextends\\b"), + QStringLiteral("\\bexternal\\b"), QStringLiteral("\\bfinal\\b"), QStringLiteral("\\bforall\\b"), + QStringLiteral("\\bformat\\b"), QStringLiteral("\\bfunction\\b"), QStringLiteral("\\bgeneric\\b"), + QStringLiteral("\\bgoto\\b"), QStringLiteral("\\bif\\b"), QStringLiteral("\\bimplicit\\b"), + QStringLiteral("\\bimport\\b"), QStringLiteral("\\binclude\\b"), QStringLiteral("\\binout\\b"), + QStringLiteral("\\binquire\\b"), QStringLiteral("\\binteger\\b"), QStringLiteral("\\bintent\\b"), + QStringLiteral("\\binterface\\b"), QStringLiteral("\\bintrinsic\\b"), QStringLiteral("\\bkind\\b"), + QStringLiteral("\\blen\\b"), QStringLiteral("\\blogical\\b"), QStringLiteral("\\bmodule\\b"), + QStringLiteral("\\bnamelist\\b"), QStringLiteral("\\bnone\\b"), QStringLiteral("\\bnullify\\b"), + QStringLiteral("\\bonly\\b"), QStringLiteral("\\bopen\\b"), QStringLiteral("\\boperator\\b"), + QStringLiteral("\\boptional\\b"), QStringLiteral("\\bparameter\\b"), QStringLiteral("\\bpointer\\b"), + QStringLiteral("\\bprecision\\b"), QStringLiteral("\\bprint\\b"), QStringLiteral("\\bprivate\\b"), + QStringLiteral("\\bprocedure\\b"), QStringLiteral("\\bprogram\\b"), QStringLiteral("\\bprotected\\b"), + QStringLiteral("\\bpublic\\b"), QStringLiteral("\\bpure\\b"), QStringLiteral("\\bread\\b"), + QStringLiteral("\\breal\\b"), QStringLiteral("\\brecursive\\b"), QStringLiteral("\\bresult\\b"), + QStringLiteral("\\breturn\\b"), QStringLiteral("\\brewind\\b"), QStringLiteral("\\bsave\\b"), + QStringLiteral("\\bselect\\b"), QStringLiteral("\\bsequence\\b"), QStringLiteral("\\bstop\\b"), + QStringLiteral("\\bsubmodule\\b"), QStringLiteral("\\bsubroutine\\b"), QStringLiteral("\\btarget\\b"), + QStringLiteral("\\bthen\\b"), QStringLiteral("\\btype\\b"), QStringLiteral("\\buse\\b"), + QStringLiteral("\\bvalue\\b"), QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bwhere\\b"), + QStringLiteral("\\bwhile\\b"), QStringLiteral("\\bwrite\\b"), + // Dotted logical/relational operators and constants. (The symbolic + // forms '==', '/=', ... are left unstyled, like in the C++ highlighter.) + QStringLiteral("\\.and\\."), QStringLiteral("\\.or\\."), QStringLiteral("\\.not\\."), + QStringLiteral("\\.eqv\\."), QStringLiteral("\\.neqv\\."), QStringLiteral("\\.true\\."), + QStringLiteral("\\.false\\."), QStringLiteral("\\.eq\\."), QStringLiteral("\\.ne\\."), + QStringLiteral("\\.lt\\."), QStringLiteral("\\.le\\."), QStringLiteral("\\.gt\\."), + QStringLiteral("\\.ge\\."), + }; + + _highlightingRules.clear(); // Clear old rules. + + HighlightingRule rule; + + // No class rule: Fortran is case insensitive with no reliable case convention, + // and derived types are already covered by the 'type'/'class' keywords. + + // No function rule either: 'name(...)' is also how Fortran references arrays, + // so highlighting it as a function call would be wrong half of the time. + + // Set keywords format and expression. + // Fortran is case insensitive, so we use case insensitive option. + for (const QString &pattern : keywordPatterns) { + rule.pattern = QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption); + rule.format = _keywordFormat; + _highlightingRules.append(rule); + } + + // Set keyword format for the symbolic operators too, so '==' renders like + // its word form '.eq.'. Safe in Fortran: no templates, '<'/'>' are always + // relational. Two-character operators first in the alternation. + rule.pattern = QRegularExpression(QStringLiteral("//|==|/=|<=|>=|=>|<|>")); + rule.format = _keywordFormat; + _highlightingRules.append(rule); + + // Set function format for the common intrinsics, when actually called. + // A generic before-parenthesis rule is out (see above), but a closed list + // of intrinsics followed by '(' is safe. As a side effect, 'len'/'kind' + // stay keywords in declarations ('len=32') and become functions in calls. + rule.pattern = QRegularExpression(QStringLiteral( + "\\b(?:abs|achar|adjustl|adjustr|aimag|allocated|associated|atan2?|ceiling|char|cmplx|conjg|cosh?|count" + "|cpu_time|dble|dot_product|epsilon|exp|floor|huge|iachar|index|int|kind|lbound|len|len_trim|log|log10" + "|matmul|max|maxval|merge|min|minval|mod|modulo|nint|norm2|pack|present|product|random_number|random_seed" + "|real|repeat|reshape|scan|sign|sinh?|size|spread|sqrt|sum|system_clock|tanh?|tiny|transfer|transpose" + "|trim|ubound|unpack|verify)\\b(?=\\s*\\()"), QRegularExpression::CaseInsensitiveOption); + rule.format = _functionFormat; + _highlightingRules.append(rule); + + // Set quote format and expression. Fortran strings use either quote character. + rule.pattern = QRegularExpression(QStringLiteral("\"[^\"]*\"")); + rule.format = _quotationFormat; + _highlightingRules.append(rule); + + rule.pattern = QRegularExpression(QStringLiteral("'[^']*'")); + rule.format = _quotationFormat; + _highlightingRules.append(rule); + + // Set single line comment format and expression. (Fortran uses !) + // Note '//' is the string concatenation operator in Fortran, not a comment. + rule.pattern = QRegularExpression(QStringLiteral("![^\n]*")); + rule.format = _singleLineCommentFormat; + _highlightingRules.append(rule); + + // Multi-line comments are not possible in Fortran. + // Using a regex that matches nothing to avoid any unintended highlighting. + _commentStartExpression = QRegularExpression(QStringLiteral("(?!)")); + _commentEndExpression = QRegularExpression(QStringLiteral("(?!)")); +} diff --git a/src/SeerFortranSourceHighlighter.h b/src/SeerFortranSourceHighlighter.h new file mode 100644 index 0000000..d916560 --- /dev/null +++ b/src/SeerFortranSourceHighlighter.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2026 Ernie Pasveer +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "SeerHighlighterSettings.h" +#include "SeerSourceHighlighter.h" + +class SeerFortranSourceHighlighter : public SeerSourceHighlighter { + + Q_OBJECT + + public: + SeerFortranSourceHighlighter(QTextDocument *parent = 0); + + virtual void setHighlighterSettings (const SeerHighlighterSettings &settings) override; +}; diff --git a/src/SeerHighlighterSettings.cpp b/src/SeerHighlighterSettings.cpp index 35e43a1..442c56a 100644 --- a/src/SeerHighlighterSettings.cpp +++ b/src/SeerHighlighterSettings.cpp @@ -23,10 +23,11 @@ SeerHighlighterSettings& SeerHighlighterSettings::operator= (const SeerHighlight _keys = rhs._keys; _formats = rhs._formats; - _adaSourceSuffixes = rhs._adaSourceSuffixes; - _cppSourceSuffixes = rhs._cppSourceSuffixes; - _rustSourceSuffixes = rhs._rustSourceSuffixes; - _odinSourceSuffixes = rhs._odinSourceSuffixes; + _adaSourceSuffixes = rhs._adaSourceSuffixes; + _cppSourceSuffixes = rhs._cppSourceSuffixes; + _fortranSourceSuffixes = rhs._fortranSourceSuffixes; + _rustSourceSuffixes = rhs._rustSourceSuffixes; + _odinSourceSuffixes = rhs._odinSourceSuffixes; return *this; } @@ -87,6 +88,11 @@ void SeerHighlighterSettings::setCppSourceSuffixes (const QString& suffixes) { _cppSourceSuffixes = suffixes; } +void SeerHighlighterSettings::setFortranSourceSuffixes (const QString& suffixes) { + + _fortranSourceSuffixes = suffixes; +} + void SeerHighlighterSettings::setOdinSourceSuffixes (const QString& suffixes) { _odinSourceSuffixes = suffixes; @@ -102,6 +108,11 @@ const QString& SeerHighlighterSettings::cppSourceSuffixes () { return _cppSourceSuffixes; } +const QString& SeerHighlighterSettings::fortranSourceSuffixes () { + + return _fortranSourceSuffixes; +} + const QString& SeerHighlighterSettings::odinSourceSuffixes () { return _odinSourceSuffixes; @@ -245,6 +256,7 @@ SeerHighlighterSettings SeerHighlighterSettings::populate_light () { languageSettings.setAdaSourceSuffixes(".ada|.adb|.ads"); languageSettings.setCppSourceSuffixes(".c|.C|.cpp|.CPP|.cxx|.CXX|.h|.H|.hpp|.hxx|.Hxx|.HXX"); + languageSettings.setFortranSourceSuffixes(".f90|.F90|.f95|.F95|.f03|.F03|.f08|.F08"); languageSettings.setOdinSourceSuffixes(".odin"); languageSettings.setRustSourceSuffixes(".rs"); @@ -343,6 +355,7 @@ SeerHighlighterSettings SeerHighlighterSettings::populate_dark () { languageSettings.setAdaSourceSuffixes(".ada|.adb|.ads"); languageSettings.setCppSourceSuffixes(".c|.C|.cpp|.CPP|.cxx|.CXX|.h|.H|.hpp|.hxx|.Hxx|.HXX"); + languageSettings.setFortranSourceSuffixes(".f90|.F90|.f95|.F95|.f03|.F03|.f08|.F08"); languageSettings.setOdinSourceSuffixes(".odin"); languageSettings.setRustSourceSuffixes(".rs"); diff --git a/src/SeerHighlighterSettings.h b/src/SeerHighlighterSettings.h index 8445341..5bbe76b 100644 --- a/src/SeerHighlighterSettings.h +++ b/src/SeerHighlighterSettings.h @@ -25,10 +25,12 @@ class SeerHighlighterSettings { int count () const; void setAdaSourceSuffixes (const QString& suffixes); void setCppSourceSuffixes (const QString& suffixes); + void setFortranSourceSuffixes(const QString& suffixes); void setOdinSourceSuffixes (const QString& suffixes); void setRustSourceSuffixes (const QString& suffixes); const QString& adaSourceSuffixes (); const QString& cppSourceSuffixes (); + const QString& fortranSourceSuffixes (); const QString& odinSourceSuffixes (); const QString& rustSourceSuffixes (); @@ -42,6 +44,7 @@ class SeerHighlighterSettings { QList _formats; QString _adaSourceSuffixes; QString _cppSourceSuffixes; + QString _fortranSourceSuffixes; QString _odinSourceSuffixes; QString _rustSourceSuffixes; }; diff --git a/src/SeerMainWindow.cpp b/src/SeerMainWindow.cpp index 7f38d52..f3f534a 100644 --- a/src/SeerMainWindow.cpp +++ b/src/SeerMainWindow.cpp @@ -1800,10 +1800,11 @@ void SeerMainWindow::writeConfigSettings () { } settings.endGroup(); } - settings.setValue("cppsuffixes", highlighter.cppSourceSuffixes()); - settings.setValue("odinsuffixes", highlighter.odinSourceSuffixes()); - settings.setValue("rustsuffixes", highlighter.rustSourceSuffixes()); - settings.setValue("adasuffixes", highlighter.adaSourceSuffixes()); + settings.setValue("cppsuffixes", highlighter.cppSourceSuffixes()); + settings.setValue("odinsuffixes", highlighter.odinSourceSuffixes()); + settings.setValue("rustsuffixes", highlighter.rustSourceSuffixes()); + settings.setValue("adasuffixes", highlighter.adaSourceSuffixes()); + settings.setValue("fortransuffixes", highlighter.fortranSourceSuffixes()); } settings.endGroup(); } settings.endGroup(); @@ -1918,10 +1919,11 @@ void SeerMainWindow::readConfigSettings () { } settings.endGroup(); } - if (settings.contains("cppsuffixes")) highlighter.setCppSourceSuffixes(settings.value("cppsuffixes").toString()); - if (settings.contains("odinsuffixes")) highlighter.setOdinSourceSuffixes(settings.value("odinsuffixes").toString()); - if (settings.contains("rustsuffixes")) highlighter.setRustSourceSuffixes(settings.value("rustsuffixes").toString()); - if (settings.contains("adasuffixes")) highlighter.setAdaSourceSuffixes(settings.value("adasuffixes").toString()); + if (settings.contains("cppsuffixes")) highlighter.setCppSourceSuffixes(settings.value("cppsuffixes").toString()); + if (settings.contains("odinsuffixes")) highlighter.setOdinSourceSuffixes(settings.value("odinsuffixes").toString()); + if (settings.contains("rustsuffixes")) highlighter.setRustSourceSuffixes(settings.value("rustsuffixes").toString()); + if (settings.contains("adasuffixes")) highlighter.setAdaSourceSuffixes(settings.value("adasuffixes").toString()); + if (settings.contains("fortransuffixes")) highlighter.setFortranSourceSuffixes(settings.value("fortransuffixes").toString()); gdbWidget->editorManager()->setEditorHighlighterSettings(highlighter); diff --git a/src/SeerSourceHighlighter.cpp b/src/SeerSourceHighlighter.cpp index 2c7c876..7c6fa7a 100644 --- a/src/SeerSourceHighlighter.cpp +++ b/src/SeerSourceHighlighter.cpp @@ -6,6 +6,7 @@ #include "SeerAdaSourceHighlighter.h" #include "SeerOdinSourceHighlighter.h" #include "SeerCppSourceHighlighter.h" +#include "SeerFortranSourceHighlighter.h" #include "SeerRustSourceHighlighter.h" SeerSourceHighlighter::SeerSourceHighlighter (QTextDocument* parent) : QSyntaxHighlighter(parent) {} @@ -26,6 +27,11 @@ SeerSourceHighlighter* SeerSourceHighlighter::getSourceHighlighter(QString const return new SeerCppSourceHighlighter(0); } + QRegularExpression fortran_re("(?:" + settings.fortranSourceSuffixes() + ")$"); + if (file.contains(fortran_re)) { + return new SeerFortranSourceHighlighter(0); + } + QRegularExpression odin_re("(?:" + settings.odinSourceSuffixes() + ")$"); if (file.contains(odin_re)) { return new SeerOdinSourceHighlighter(0); diff --git a/tests/hellofortran90/Makefile b/tests/hellofortran90/Makefile index fcd1183..477ff11 100644 --- a/tests/hellofortran90/Makefile +++ b/tests/hellofortran90/Makefile @@ -1,5 +1,5 @@ # Fortran compiler -FC = gfortran-12 +FC = gfortran FFLAGS = -g # Source files