diff --git a/src/musredit_qt6/musredit/CMakeLists.txt b/src/musredit_qt6/musredit/CMakeLists.txt index 03347d837..f2d20f89f 100644 --- a/src/musredit_qt6/musredit/CMakeLists.txt +++ b/src/musredit_qt6/musredit/CMakeLists.txt @@ -42,6 +42,7 @@ set(musredit_src PGetPlotBlockDialog.cpp PMsr2DataDialog.cpp PChangeDefaultPathsDialog.cpp + PMsrHighlighter.cpp PMusrEditAbout.cpp main.cpp ) diff --git a/src/musredit_qt6/musredit/PMsrHighlighter.cpp b/src/musredit_qt6/musredit/PMsrHighlighter.cpp new file mode 100644 index 000000000..9b235e188 --- /dev/null +++ b/src/musredit_qt6/musredit/PMsrHighlighter.cpp @@ -0,0 +1,471 @@ +/** + * @file PMsrHighlighter.cpp + * @brief Implementation of the MsrHighlighter class for musredit. + * + * This file contains the implementation of a custom syntax highlighter for + * (.msr) files, to be used by musredit. It identifies, for example, blocks, + * comments, and predefined keywords. + * + * @author Ryan M. L. McFadden (rmlm@triumf.ca) + * @date June 2026 + */ + +/*************************************************************************** + * Copyright (C) 2007-2026 by Andreas Suter * + * andreas.suter@psi.ch * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "PMsrHighlighter.h" + +/** + * @brief Initializes a new instance of the syntax highlighting compiler engine. + * + * Sets up structural text formatting rules (fonts, colors, and styles) and + * compiles matching regex patterns for blocks, keywords, and comments before + * appending them to the main lookup vector. + */ +PMsrHighlighter::PMsrHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) { + + // ========================================================================= + // TABLEAU COLOURS + // ========================================================================= + + // Individual QColor definitions grouped by color pairs (Dark / Light) + const QColor tabBlue("#1F77B4"); + const QColor tabBlueLight("#AEC7E8"); + + const QColor tabOrange("#FF7F0E"); + const QColor tabOrangeLight("#FFBB78"); + + const QColor tabGreen("#2CA02C"); + const QColor tabGreenLight("#98DF8A"); + + const QColor tabRed("#D62728"); + const QColor tabRedLight("#FF9896"); + + const QColor tabPurple("#9467BD"); + const QColor tabPurpleLight("#C5B0D5"); + + const QColor tabBrown("#8C564B"); + const QColor tabBrownLight("#C49C94"); + + const QColor tabPink("#E377C2"); + const QColor tabPinkLight("#F7B6D2"); + + const QColor tabGray("#7F7F7F"); + const QColor tabGrayLight("#C7C7C7"); + + const QColor tabOlive("#BCBD22"); + const QColor tabOliveLight("#DBDB8D"); + + const QColor tabCyan("#17BECF"); + const QColor tabCyanLight("#9EDAE5"); + + // ========================================================================= + // KEYWORD STYLING + // ========================================================================= + + // Define the colour/styling for each keyword type. + + blockFormat.setForeground(tabOlive); // BLOCK keywords + theoryFormat.setForeground(tabGreen); // THEORY keywords + functionsFormat.setForeground(tabRed); // FUNCTION keywords + parFormat.setForeground(tabPink); // parX keywords + mapFormat.setForeground(tabOrange); // mapX keywords + funFormat.setForeground(tabPurple); // funX keywords + runFormat.setForeground(tabGreen); // RUN/GLOBAL keywords + commandsFormat.setForeground(tabGreen); // COMMANDS keywords + fourierFormat.setForeground(tabGreen); // FOURIER keywords + plotFormat.setForeground(tabGreen); // PLOT keywords + optionsFormat.setForeground(tabRed); // keywords input options + infoFormat.setForeground(tabGray); // auto-printed information + boolFormat.setForeground(tabRed); // boolean strings + nullFormat.setForeground(tabRed); // null strings + dateFormat.setForeground(tabPink); // date & time strings + numberFormat.setForeground(tabBlue); // integer & float strings + commentFormat.setForeground(tabGray); // comments + commentFormat.setFontItalic(true); + titleFormat.setForeground(Qt::black); // title + // titleFormat.setBackground(tabGrayLight); + + // ========================================================================= + // BLOCK HEADERS + // ========================================================================= + QStringList block_keywords = {"FITPARAMETER", "THEORY", "FUNCTIONS", + "GLOBAL", "RUN", "COMMANDS", + "FOURIER", "PLOT", "STATISTIC"}; + addRule(block_keywords, blockFormat); + + // ========================================================================= + // THEORY KEYWORDS + // ========================================================================= + QStringList theory_keywords_long = { + "const", "asymmetry", "simplExpo", "generExpo", + "simpleGss", "statGssKT", "statGssKTLF", "dynGssKTLF", + "statExpKT", "statExpKTLF", "dynExpKTLF", "dynGLKT_F_ZF", + "dynGLKT_F_LF", "dynGLKT_LF", "combiLGKT", "strKT", + "spinGlass", "rdAnisoHf", "TFieldCos", "internFld", + "Bessel", "internbsl", "internFldGK", "internFldLL", + "F_mu_F", "abragam", "skewedGss", "staticNKZF", + "staticNKTF", "dynamicNKZF", "dynamicNKTF", "muMinusExpTF", + "polynom"}; + + QStringList theory_keywords_short = {"c", + "a", + "se", + "ge", + "stg", + "sgktlf", + "dgktlf", + "sekt", + "sektlf", + "dektlf", + "dglktfzf", + "dglktflf", + "dglktlf", + "lgkt", + "skt", + "spg", + "rahf", + "tf", + "ifld", + "b", + "ib", + "ifgk", + "ifll", + "fmuf", + "ab" + "skg", + "snkzf", + "snktf", + "snkzf", + "dnkzf", + "dnktf", + "mmsetf", + "p"}; + + // combine both theory keyword lists + QStringList theory_keywords = theory_keywords_long + theory_keywords_short; + + addRule(theory_keywords, theoryFormat); + + // ========================================================================= + // FUNCTIONS KEYWORDS + // ========================================================================= + QStringList functions_keywords = { + "gamma_mu", "pi", "B", "b", "En", "en", "T\\d+", + /* "+", "-", "*", "/", */ + }; + addRule(functions_keywords, functionsFormat); + + // ========================================================================= + // funX KEYWORDS + // ========================================================================= + QStringList fun_keywords = {"fun\\d+"}; + addRule(fun_keywords, funFormat); + + // ========================================================================= + // parX KEYWORDS + // ========================================================================= + QStringList par_keywords = {"par\\d+"}; + addRule(par_keywords, parFormat); + + // ========================================================================= + // mapX KEYWORDS + // ========================================================================= + QStringList map_keywords = {"map\\d+"}; + addRule(map_keywords, mapFormat); + + // ========================================================================= + // RUN KEYWORDS + // ========================================================================= + QStringList run_keywords = { + "fittype", "data", "t0", "addt0", + "deadtime-cor", "fit", "packing", "rrf_freq", + "rrf_packing", "rrf_phase", "alpha", "beta", + "norm", "backgr.fit", "lifetime", "lifetimecorrection", + "map", "forward", "backward", "backgr.fix", + "background", "xy-data"}; + addRule(run_keywords, runFormat); + + // ========================================================================= + // FOURIER KEYWORDS + // ========================================================================= + QStringList fourier_keywords = {"units", + "fourier_power", + "dc-corrected", + "apodization", + "plot", + "phase", + "range_for_phase_correction", + "range"}; + addRule(fourier_keywords, fourierFormat); + + // ========================================================================= + // PLOT KEYWORDS + // ========================================================================= + QStringList plot_keywords = {"lifetimecorrection", + "runs", + "range", + "sub_ranges", + "use_fit_ranges", + "view_packing", + "logx", + "logy", + "rrf_packing value", + "rrf_freq value unit", + "rrf_phase value"}; + addRule(plot_keywords, plotFormat); + + // ========================================================================= + // UNITS OPTIONS + // ========================================================================= + QStringList units_options = {"Gauss", "Tesla", "MHz", "Mc/s"}; + + // ========================================================================= + // APODIZATION OPTIONS + // ========================================================================= + QStringList apodization_options = {"NONE", "WEAK", "MEDIUM", "STRONG"}; + + // ========================================================================= + // RRF OPTIONS + // ========================================================================= + QStringList rrf_options = {"MHz", "Mc", "T"}; + + // ========================================================================= + // FOURIER OPTIONS + // ========================================================================= + QStringList fourier_options = {"REAL", "IMAG", "REAL_AND_IMAG", + "POWER", "PHASE", "PHASE_OPT_REAL"}; + + // ========================================================================= + // COMMANDS OPTIONS + // ========================================================================= + QStringList commands_options = {"RESET"}; + + // ========================================================================= + // ALL OPTIONS + // ========================================================================= + QStringList all_options = units_options + apodization_options + rrf_options + + fourier_options + commands_options; + + addRule(all_options, optionsFormat); + + // ========================================================================= + // FIT INFO + // ========================================================================= + QStringList fit_info = { + "(single histogram fit)", "(single histogram RRF fit)", + "(asymmetry fit)", "(asymmetry RRF fit)", + "(mu minus fit)", "(beta-NMR fit)", + "(non muSR fit)"}; + + // ========================================================================= + // PLOT INFO + // ========================================================================= + QStringList plot_info = {"(single histo plot)", "(single histo RRF plot)", + "(asymmetry plot)", "(asymmetry RRF plot)", + "(mu minus plot)", "(beta-NMR asymmetry plot)", + "(non muSR plot)"}; + + // ========================================================================= + // RUN INFO + // ========================================================================= + QStringList run_info = {"(name beamline institute data-file-format)"}; + + // ========================================================================= + // THEORY INFO + // ========================================================================= + QStringList theory_info = {"(N0 tau A lambda phase nu tshift)", + "(N0 tau A lambda phase nu)", + "(damping hopping-rate tshift)", + "(damping hopping-rate)", + "(damping_D0 R_b nu_c tshift)", + "(damping_D0 R_b nu_c)", + "(damping_D0 R_b tshift)", + "(damping_D0 R_b)", + "(fraction frequency sigma lambda beta tshift)", + "(fraction frequency sigma lambda beta)", + "(fraction phase frequency Trate Lrate tshift)", + "(fraction phase frequency Trate Lrate)", + "(frequency damping hopping-rate tshift)", + "(frequency damping hopping-rate)", + "(frequency damping tshift)", + "(frequency damping)", + "(frequency rate tshift)", + "(frequency rate)", + "(frequency tshift)", + "(frequency)", + "(lorentzRate gaussRate tshift)", + "(lorentzRate gaussRate)", + "(phase frequency damping_D0 R_b nu_c tshift)", + "(phase frequency damping_D0 R_b nu_c)", + "(phase frequency damping_D0 R_b tshift)", + "(phase frequency damping_D0 R_b)", + "(phase frequency rate_m rate_p tshift)", + "(phase frequency rate_m rate_p)", + "(phase frequency tshift)", + "(phase frequency)", + "(rate beta tshift)", + "(rate beta)", + "(rate exponent tshift)", + "(rate exponent)", + "(rate hopprate order tshift)", + "(rate hopprate order)", + "(rate hopprate tshift)", + "(rate hopprate)", + "(rate tshift)", + "(rate)", + "(tshift p0 p1 ... pn)"}; + + // ========================================================================= + // ALL INFO + // ========================================================================= + QStringList all_info = fit_info + run_info + plot_info + theory_info; + + addRule(all_info, infoFormat); + + // ========================================================================= + // COMMANDS BLOCK KEYWORDS + // ========================================================================= + QStringList commands_keywords = { + "STRATEGY", "MIGRAD", "SIMPLEX", "MINIMIZE", + "MINOS", "HESSE", "SAVE", "SET BATCH", + "END RETURN", "MAX_LIKELIHOOD", "PRINT_LEVEL", "SCAN", + "CONTOURS", "MNPLOT", "FIX", "RELEASE", + "FIT_RANGE", "SCALE_N0_BKG", "SECTOR", "OpenMP", + "CUDA", "OpenCL-CPU", "OpenCL-GPU"}; + + addRule(commands_keywords, commandsFormat); + + // ========================================================================= + // BOOLEANS + // ========================================================================= + QStringList bool_keywords = {"true", "false"}; + addRule(bool_keywords, boolFormat); + + // ========================================================================= + // NULLS + // ========================================================================= + QStringList null_keywords = {"none"}; + addRule(null_keywords, nullFormat); + + // ========================================================================= + // NUMBERS + // ========================================================================= + // Distinguish integer/decimal numbers from raw text. + HighlightingRule number_rule; + QRegularExpression number_regex("\\b-?\\d*\\.?\\d+\\b"); + number_rule.pattern = number_regex; + number_rule.format = numberFormat; + highlightingRules.append(number_rule); + + // ========================================================================= + // DATES + // ========================================================================= + // Distinguish date/time strings from raw text. + HighlightingRule date_rule; + // date regex (for dates of the form: YYYY-MM-DD HH:mm:ss) + QRegularExpression date_regex("\\b\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[" + "01]) (0\\d|1\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\b"); + date_rule.pattern = date_regex; + date_rule.format = dateFormat; + highlightingRules.append(date_rule); + + // ========================================================================= + // INLINE AND LINE COMMENTS + // ========================================================================= + // Musrfit treats lines beginning with '#' as ignored comments. + // This matches a literal '#' symbol and captures everything remaining on that + // line. Styled gray and italicized to quickly indicate non-executable text + // annotations. + HighlightingRule comment_rule; + QRegularExpression comment_regex("#.*"); + comment_rule.pattern = comment_regex; + comment_rule.format = commentFormat; + highlightingRules.append(comment_rule); +} + +/** + * @brief Triggers formatting checks against text segments whenever they refresh + * on screen. + * + * Loops through the rules sequentially. If a match occurs, the method + * calculates the starting point index and length, passing this information back + * to the Qt layout interface engine. + */ +void PMsrHighlighter::addRule(const QStringList &keywords, + const QTextCharFormat formatting) { + // Instantiate the HighlighingRule struct. + HighlightingRule rule; + + // loop through every keyword + for (const QString &keyword : keywords) { + // Wrap each keyword string into a strict word-boundary (\b) regular + // expression. This ensures keywords matching inside longer variable titles + // do not falsely trigger formatting. + QRegularExpression regex(QString("\\b%1\\b").arg(keyword)); + + // apply the formatting to the rule struct + rule.pattern = regex; + rule.format = formatting; + + // add the rule to the main list + this->highlightingRules.append(rule); + } +} + +/** + * @brief Triggers formatting checks against text segments whenever they refresh + * on screen. + * + * Loops through the rules sequentially. If a match occurs, the method + * calculates the starting point index and length, passing this information back + * to the Qt layout interface engine. + */ +void PMsrHighlighter::highlightBlock(const QString &text) { + + // Sequentially process each entry stored inside the rules vector array. + for (const HighlightingRule &rule : highlightingRules) { + // Execute a global search through the single block string provided. + QRegularExpressionMatchIterator matchIterator = + rule.pattern.globalMatch(text); + + // Loop over every match location instance discovered within this single + // text string. + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + + // Extract the position details of the substring match sequence. + int index = match.capturedStart(); + int length = match.capturedLength(); + + // Core Qt framework utility method which maps styles directly onto + // coordinates of the running open source buffer segment. + setFormat(index, length, rule.format); + } + } + + // Treat highlighting of the TITLE block separately so that it take precedence + // over all other rules. + if (currentBlock().blockNumber() == 0) { + // applies the title formatting for the entirety of the first line + setFormat(0, text.length(), titleFormat); + } +} diff --git a/src/musredit_qt6/musredit/PMsrHighlighter.h b/src/musredit_qt6/musredit/PMsrHighlighter.h new file mode 100644 index 000000000..2f14aa79b --- /dev/null +++ b/src/musredit_qt6/musredit/PMsrHighlighter.h @@ -0,0 +1,125 @@ +/** + * @file PMsrHighlighter.h + * @brief Definition of the MsrHighlighter class for musredit. + * + * This file contains the definition of a custom syntax highlighter for + * (.msr) files, to be used by musredit. It identifies, for example, blocks, + * comments, and predefined keywords. + * + * @author Ryan M. L. McFadden (rmlm@triumf.ca) + * @date June 2026 + */ + +/*************************************************************************** + * Copyright (C) 2007-2026 by Andreas Suter * + * andreas.suter@psi.ch * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _PMSRHIGHLIGHTER_H_ +#define _PMSRHIGHLIGHTER_H_ + +#include +#include +#include +#include +#include + +/** + * @class MsrHighlighter + * @brief Custom syntax highlighter for .msr (musrfit configuration) file + * syntax. + * + * This class inherits from QSyntaxHighlighter to intercept a QTextDocument's + * layout. It applies custom fonts, weights, and colors to specific structural + * components of the text document via regular expression rules. + */ +class PMsrHighlighter : public QSyntaxHighlighter { + Q_OBJECT + +public: + /** + * @brief Constructs a PMsrHighlighter object and attaches it to a target + * document. + * + * @param parent The parent QTextDocument that this engine will dynamically + * format. + */ + PMsrHighlighter(QTextDocument *parent = nullptr); + +protected: + /** + * @brief Automatically called by the Qt rendering engine to format lines of + * text. + * + * Iterates through the registered regular expression rule vectors and + * overrides the text layout style wherever a character sequence matches a + * rule. + * + * @param text A single block (typically a line) of text provided by the + * editor. + */ + void highlightBlock(const QString &text) override; + +private: + /** + * @struct HighlightingRule + * @brief Container grouping a lookup pattern with its visual theme mapping. + */ + struct HighlightingRule { + QRegularExpression + pattern; ///< The compiled regular expression pattern to search for. + QTextCharFormat + format; ///< The text style (color, font, weight) applied upon match. + }; + + /** + * @brief A dynamic array tracking all active syntax parsing rules. + */ + QVector highlightingRules; + + /** + * @brief Helper function for passing formatting information to the + * highlightingRules dynamic array. + */ + void addRule(const QStringList &keywords, const QTextCharFormat formatting); + + QTextCharFormat blockFormat; ///< Formatting profile for BLOCK keywords. + QTextCharFormat titleFormat; ///< Formatting profile for TITLE line. + QTextCharFormat theoryFormat; ///< Formatting profile for THEORY keywords. + QTextCharFormat + functionsFormat; ///< Formatting profile for FUNCTION keywords. + QTextCharFormat parFormat; ///< Formatting profile for parX keywords. + QTextCharFormat mapFormat; ///< Formatting profile mapX keywords. + QTextCharFormat funFormat; ///< Formatting profile funX keywords. + QTextCharFormat runFormat; ///< Formatting profile RUN/GLOBAL keywords. + QTextCharFormat commandsFormat; ///< Formatting profile COMMANDS keywords. + QTextCharFormat fourierFormat; ///< Formatting profile FOURIER keywords. + QTextCharFormat plotFormat; ///< Formatting profile PLOT keywords. + QTextCharFormat + optionsFormat; ///< Formatting profile for keyword input options. + QTextCharFormat + infoFormat; ///< Formatting profile for auto-printed information. + QTextCharFormat boolFormat; ///< Formatting profile for boolean strings. + QTextCharFormat nullFormat; ///< Formatting profile for null strings. + QTextCharFormat dateFormat; ///< Formatting profile for date & time strings + QTextCharFormat + numberFormat; ///< Formatting profile for integer & float strings. + QTextCharFormat commentFormat; ///< Formatting profile for comments. +}; + +#endif // _PMSRHIGHLIGHTER_H_ diff --git a/src/musredit_qt6/musredit/PSubTextEdit.cpp b/src/musredit_qt6/musredit/PSubTextEdit.cpp index 353ac7998..4e05dd284 100644 --- a/src/musredit_qt6/musredit/PSubTextEdit.cpp +++ b/src/musredit_qt6/musredit/PSubTextEdit.cpp @@ -66,6 +66,7 @@ #include "PGetNonMusrRunBlockDialog.h" #include "PGetFourierBlockDialog.h" #include "PGetPlotBlockDialog.h" +#include "PMsrHighlighter.h" //---------------------------------------------------------------------------------------------------- /** @@ -101,6 +102,10 @@ PSubTextEdit::PSubTextEdit(PAdmin *admin, QWidget *parent) : QPlainTextEdit(parent), fAdmin(admin) { + // Pass the current text document to the highlighter. This ensures that every + // time a new .msr file or tab is spawned, the document registers the parser + // formatting. + PMsrHighlighter *highlight = new PMsrHighlighter(this->document()); } //----------------------------------------------------------------------------------------------------