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