Compare commits

...

3 Commits

Author SHA1 Message Date
Ernie Pasveer 04b5cf519f Fixed example Fortran90 sample in Editor config widget. 2026-08-12 18:01:16 -05:00
Ernie Pasveer 781d57d8d9 Merge pull request #516 from tiresiasfromthebai/fortran-highlighter
Add a Fortran syntax highlighter
2026-08-12 17:42:21 -05:00
tiresiasfromthebai 2f2b48f714 Add a Fortran syntax highlighter
Free-form Fortran (90 and later): case-insensitive keywords, dotted and
symbolic operators, both string delimiters, '!' comments ('//' is string
concatenation, not a comment), common intrinsics as functions. Plus the
usual plumbing: suffix settings, editor config page tab, save/restore,
highlighter factory dispatch.

Also let tests/hellofortran90 build with the default 'gfortran' instead
of a hardcoded 'gfortran-12'.
2026-08-10 12:13:08 +02:00
10 changed files with 225 additions and 13 deletions
+2
View File
@@ -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
+15
View File
@@ -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);
@@ -413,6 +417,17 @@ void SeerEditorConfigPage::handleLanguageTabChanged (int index) {
" Put_Line (\"Hello, Seer!\"); -- Greetings\n"
"end Main;",
"sample.adb");
} else if (index == 4) { // Fortran90
editorWidget->sourceArea()->openText("!\n"
"! Seer, Copyright 2026 (c)\n"
"! Ernie Pasveer (epasveer@att.net)\n"
"! Fortran90 example\n"
"!\n"
"program hello_world\n"
" implicit none\n"
" print *, \"Hello, World!\"\n"
"end program hello_world\n",
"sample.f90");
}
editorWidget->sourceArea()->setCurrentLine(0);
+27
View File
@@ -272,6 +272,33 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabFortran">
<attribute name="title">
<string>Fortran</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayoutFortran">
<item>
<widget class="QLabel" name="fortranLabel">
<property name="text">
<string>Suffixes:</string>
</property>
</widget>
</item>
<item>
<widget class="QHistoryLineEdit" name="fortranSuffixesLineEdit">
<property name="toolTip">
<string>Fortran source suffixes. Separated by '|'.</string>
</property>
<property name="placeholderText">
<string>Fortran source suffixes.</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="3" column="0">
+126
View File
@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: 2026 Ernie Pasveer <epasveer@att.net>
//
// 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("(?!)"));
}
+18
View File
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2026 Ernie Pasveer <epasveer@att.net>
//
// 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;
};
+17 -4
View File
@@ -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");
+3
View File
@@ -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<QTextCharFormat> _formats;
QString _adaSourceSuffixes;
QString _cppSourceSuffixes;
QString _fortranSourceSuffixes;
QString _odinSourceSuffixes;
QString _rustSourceSuffixes;
};
+10 -8
View File
@@ -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);
+6
View File
@@ -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);
+1 -1
View File
@@ -1,5 +1,5 @@
# Fortran compiler
FC = gfortran-12
FC = gfortran
FFLAGS = -g
# Source files