8#include "katedocument.h"
9#include "katescriptdocument.h"
10#include "katescriptview.h"
12#include "ktexteditor_version.h"
13#include "scripttester_p.h"
17#include <QApplication>
18#include <QCommandLineOption>
19#include <QCommandLineParser>
20#include <QDirIterator>
24#include <QStandardPaths>
25#include <QVarLengthArray>
26#include <QtEnvironmentVariables>
34#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0)
35constexpr QStringView operator""_sv(
const char16_t *str,
size_t size)
noexcept
41using ScriptTester = KTextEditor::ScriptTester;
42using TextFormat = ScriptTester::DocumentTextFormat;
43using TestFormatOption = ScriptTester::TestFormatOption;
44using PatternType = ScriptTester::PatternType;
45using DebugOption = ScriptTester::DebugOption;
47constexpr inline ScriptTester::Format::TextReplacement defaultTextReplacement{
52constexpr inline ScriptTester::Placeholders defaultPlaceholder{
54 .selectionStart = u
'[',
56 .secondaryCursor = u
'\0',
57 .secondarySelectionStart = u
'\0',
58 .secondarySelectionEnd = u
'\0',
61constexpr inline ScriptTester::Placeholders defaultFallbackPlaceholders{
63 .selectionStart = u
'[',
65 .secondaryCursor = u
'┆',
66 .secondarySelectionStart = u
'❲',
67 .secondarySelectionEnd = u
'❳',
79struct ScriptTesterQuery {
80 ScriptTester::Format format{.debugOptions = DebugOption::WriteLocation | DebugOption::WriteFunction,
81 .testFormatOptions = TestFormatOption::None,
82 .documentTextFormat = TextFormat::ReplaceNewLineAndTabWithLiteral,
83 .documentTextFormatWithBlockSelection = TextFormat::ReplaceNewLineAndTabWithPlaceholder,
84 .textReplacement = defaultTextReplacement,
85 .fallbackPlaceholders = defaultFallbackPlaceholders,
88 .success = u
"\033[32m"_s,
89 .error = u
"\033[31m"_s,
90 .carret = u
"\033[31m"_s,
91 .debugMarker = u
"\033[31;1m"_s,
92 .debugMsg = u
"\033[31m"_s,
93 .testName = u
"\x1b[36m"_s,
94 .program = u
"\033[32m"_s,
95 .fileName = u
"\x1b[34m"_s,
96 .lineNumber = u
"\x1b[35m"_s,
97 .blockSelectionInfo = u
"\x1b[37m"_s,
98 .labelInfo = u
"\x1b[37m"_s,
99 .cursor = u
"\x1b[40;1;33m"_s,
100 .selection = u
"\x1b[40;1;33m"_s,
101 .secondaryCursor = u
"\x1b[40;33m"_s,
102 .secondarySelection = u
"\x1b[40;33m"_s,
103 .blockSelection = u
"\x1b[40;37m"_s,
104 .inSelection = u
"\x1b[4m"_s,
105 .virtualText = u
"\x1b[40;37m"_s,
106 .result = u
"\x1b[40m"_s,
107 .resultReplacement = u
"\x1b[40;36m"_s,
110 ScriptTester::Paths paths{
112 .libraries = {u
":/ktexteditor/script/libraries"_s},
118 ScriptTester::TestExecutionConfig executionConfig;
120 ScriptTester::DiffCommand diff;
130 QList<Variable> variables;
132 QStringList fileNames;
134 QByteArray xdgDataDirs;
136 DualMode dualMode = DualMode::Dual;
138 bool showPreamble =
false;
139 bool extendedDebug =
false;
140 bool restoreXdgDataDirs =
false;
152 static TrueColor fromRGB(QStringView color,
bool isBg)
154 auto toHex = [](QChar c) {
155 if (c <= u
'9' && c >= u
'0') {
156 return c.unicode() - u
'0';
158 if (c <= u
'f' && c >= u
'a') {
159 return c.unicode() - u
'a';
161 if (c <= u
'F' && c >= u
'A') {
162 return c.unicode() - u
'A';
173 if (color.
size() == 4) {
182 else if (color.
size() == 7) {
183 r = (toHex(color[1]) << 4) + toHex(color[2]);
184 g = (toHex(color[3]) << 4) + toHex(color[4]);
185 b = (toHex(color[5]) << 4) + toHex(color[6]);
193 auto *p = trueColor.ansi;
194 auto pushComponent = [&](
int color) {
196 *p++ =
"0123456789"[color / 100];
198 *p++ =
"0123456789"[color / 10];
199 }
else if (color > 9) {
200 *p++ =
"0123456789"[color / 10];
202 *p++ =
"0123456789"[color % 10];
209 trueColor.len = p - trueColor.ansi;
210 trueColor.isBg = isBg;
215 QLatin1StringView sv()
const
217 return QLatin1StringView{ansi, len};
234 qsizetype totalLen = 0;
235 bool hasDefaultColor = !defaultColor.
isEmpty();
237 if (colors.isEmpty()) {
238 result = defaultColor;
246 for (
auto &color : colors) {
248 if (color[0] <= u
'9' && color[0] >= u
'0') {
250 for (
auto c : color) {
251 if (c > u
'9' && c < u
'0' && c != u
';') {
257 const bool isBg = color.startsWith(u
"bg=");
258 auto s = isBg ? color.sliced(3) : color;
259 const bool isBright = s.startsWith(u
"bright-");
260 s = isBright ? s.sliced(7) : s;
269 const auto trueColor = TrueColor::fromRGB(s, isBg);
270 if (!trueColor.len) {
274 totalLen += 5 + trueColor.len;
275 trueColors += trueColor;
278 }
else if (s == u
"black"_sv) {
279 color = SVs{u
"30"_sv, u
"40"_sv, u
"90"_sv, u
"100"_sv}[isBg + isBright * 2];
280 }
else if (s == u
"red"_sv) {
281 color = SVs{u
"31"_sv, u
"41"_sv, u
"91"_sv, u
"101"_sv}[isBg + isBright * 2];
282 }
else if (s == u
"green"_sv) {
283 color = SVs{u
"32"_sv, u
"42"_sv, u
"92"_sv, u
"102"_sv}[isBg + isBright * 2];
284 }
else if (s == u
"yellow"_sv) {
285 color = SVs{u
"33"_sv, u
"43"_sv, u
"93"_sv, u
"103"_sv}[isBg + isBright * 2];
286 }
else if (s == u
"blue"_sv) {
287 color = SVs{u
"34"_sv, u
"44"_sv, u
"94"_sv, u
"104"_sv}[isBg + isBright * 2];
288 }
else if (s == u
"magenta"_sv) {
289 color = SVs{u
"35"_sv, u
"45"_sv, u
"95"_sv, u
"105"_sv}[isBg + isBright * 2];
290 }
else if (s == u
"cyan"_sv) {
291 color = SVs{u
"36"_sv, u
"46"_sv, u
"96"_sv, u
"106"_sv}[isBg + isBright * 2];
292 }
else if (s == u
"white"_sv) {
293 color = SVs{u
"37"_sv, u
"47"_sv, u
"97"_sv, u
"107"_sv}[isBg + isBright * 2];
295 }
else if (!isBg && !isBright && s == u
"bold"_sv) {
297 }
else if (!isBg && !isBright && s == u
"dim"_sv) {
299 }
else if (!isBg && !isBright && s == u
"italic"_sv) {
301 }
else if (!isBg && !isBright && s == u
"underline"_sv) {
303 }
else if (!isBg && !isBright && s == u
"reverse"_sv) {
305 }
else if (!isBg && !isBright && s == u
"strike"_sv) {
307 }
else if (!isBg && !isBright && s == u
"doubly-underlined"_sv) {
309 }
else if (!isBg && !isBright && s == u
"overlined"_sv) {
318 totalLen += color.size() + 1;
321 if (hasDefaultColor) {
322 totalLen += defaultColor.
size() - 2;
327 if (!hasDefaultColor) {
328 result += u
"\x1b["_sv;
330 result += defaultColor;
331 result.
back() = u
';';
337 auto const *trueColorIt = trueColors.
constData();
338 for (
const auto &color : std::as_const(colors)) {
339 if (!color.isEmpty()) {
342 result += trueColorIt->isBg ? u
"48;2;"_sv : u
"38;2;"_sv;
343 result += trueColorIt->sv();
349 result.
back() = u
'm';
351 result = defaultColor;
359 auto tr = [&app](
char const *s) {
360 return app.
translate(
"KateScriptTester", s);
363 const auto translatedFolder = tr(
"folder");
364 const auto translatedOption = tr(
"option");
365 const auto translatedPattern = tr(
"pattern");
366 const auto translatedPlaceholder = tr(
"character");
367 const auto translatedColors = tr(
"colors");
370 parser.
addPositionalArgument(tr(
"file.js"), tr(
"Test files to run. If file.js represents a folder, this is equivalent to `path/*.js`."), tr(
"file.js..."));
375 {{u
"t"_s, u
"text"_s}, tr(
"Files are treated as javascript code rather than file names.")},
380 {{u
"e"_s, u
"max-error"_s}, tr(
"Maximum number of tests that can fail before stopping.")},
381 {u
"q"_s, tr(
"Alias of --max-error=1.")},
382 {{u
"E"_s, u
"expected-failure-as-failure"_s}, tr(
"functions xcmd() and xtest() will always fail.")},
387 {{u
"s"_s, u
"script"_s},
388 tr(
"Shorcut for --command=${script}/commands --command=${script}/indentation --library=${script}/library --file=${script}/files."),
390 {{u
"c"_s, u
"command"_s}, tr(
"Adds a search folder for loadScript()."), translatedFolder},
391 {{u
"l"_s, u
"library"_s}, tr(
"Adds a search folder for require() (KTextEditor JS API)."), translatedFolder},
392 {{u
"r"_s, u
"file"_s}, tr(
"Adds a search folder for read() (KTextEditor JS API)."), translatedFolder},
393 {{u
"m"_s, u
"module"_s}, tr(
"Adds a search folder for loadModule()."), translatedFolder},
394 {{u
"I"_s, u
"indent-data-test"_s}, tr(
"Set indentation base directory for indentFiles()."), translatedFolder},
399 {{u
"D"_s, u
"diff-path"_s}, tr(
"Path of diff command."), tr(
"path")},
400 {{u
"A"_s, u
"diff-arg"_s}, tr(
"Argument for diff command. Call this option several times to set multiple parameters."), translatedOption},
405 {{u
"d"_s, u
"debug"_s},
406 tr(
"Concerning the display of the debug() function. Can be used multiple times to change multiple options.\n"
407 "- location: displays the file and line number of the call (enabled by default)\n"
408 "- function: displays the name of the function that uses debug() (enabled by default)\n"
409 "- stacktrace: show the call stack after the debug message\n"
410 "- flush: debug messages are normally buffered and only displayed in case of error. This option removes buffering\n"
411 "- extended: debug() can take several parameters of various types such as Array or Object. This behavior is specific and should not be exploited "
413 "- no-location: inverse of location\n"
414 "- no-function: inverse of function\n"
415 "- no-stacktrace: inverse of stacktrace\n"
416 "- no-flush: inverse of flush\n"
417 "- all: enable all\n"
418 "- none: disable all"),
420 {{u
"H"_s, u
"hidden-name"_s}, tr(
"Do not display test names.")},
421 {{u
"p"_s, u
"parade"_s}, tr(
"Displays all tests run or skipped. By default, only error tests are displayed.")},
422 {{u
"V"_s, u
"verbose"_s}, tr(
"Displays input and ouput on each tests. By default, only error tests are displayed.")},
423 {{u
"f"_s, u
"format"_s},
424 tr(
"Defines the document text display format:\n"
425 "- raw: no transformation\n"
426 "- js: display in literal string in javascript format\n"
427 "- literal: replaces new lines and tabs with \\n and \\t (default)\n"
428 "- placeholder: replaces new lines and tabs with placeholders specified by --newline and --tab\n"
429 "- placeholder2: replaces tabs with the placeholder specified by --tab\n"),
431 {{u
"F"_s, u
"block-format"_s}, tr(
"Same as --format, but with block selection text."), translatedOption},
436 {{u
"k"_s, u
"filter"_s}, tr(
"Only runs tests whose name matches a regular expression."), translatedPattern},
437 {u
"K"_s, tr(
"Only runs tests whose name does not matches a regular expression."), translatedPattern},
443 tr(
"Character used to replace a tab in the test display with --format=placeholder. If 2 characters are given, the second corresponds the last "
444 "character replaced. --tab='->' with tabWith=4 gives '--->'."),
445 translatedPlaceholder},
446 {{u
"N"_s, u
"nl"_s, u
"newline"_s}, tr(
"Character used to replace a new line in the test display with --format=placeholder."), translatedPlaceholder},
447 {{u
"P"_s, u
"placeholders"_s},
448 tr(
"Characters used to represent cursors or selections when the test does not specify any, or when the same character represents more than one thing. "
451 "- selection start\n"
453 "- secondary cursor\n"
454 "- secondary selection start\n"
455 "- secondary selection end\n"
462 {{u
"b"_s, u
"dual"_s},
463 tr(
"Change DUAL_MODE and ALWAYS_DUAL_MODE constants behavior:\n"
464 "- noblock: never block selection (equivalent to setConfig({blockSelection=0}))\n"
465 "- block: always block selection (equivalent to setConfig({blockSelection=1}))\n"
466 "- always-dual: DUAL_MODE = ALWAYS_DUAL_MODE\n"
467 "- no-always-dual: ALWAYS_DUAL_MODE = DUAL_MODE\n"
468 "- dual: default behavior"),
471 {u
"B"_s, tr(
"Alias of --dual=noblock.")},
473 {u
"arg"_s, tr(
"Argument add to 'argv' variable in test scripts. Call this option several times to set multiple parameters."), tr(
"arg")},
476 tr(
"Uses a different preamble than the default. The result must be a function whose first parameter is the global environment, second is 'argv' array "
477 "and 'this' refers to the internal object.\n"
478 "The {CODE} substring will be replaced by the test code."),
481 {u
"print-preamble"_s, tr(
"Show preamble.")},
484 tr(
"To ensure that tests are not disrupted by system files, the XDG_DATA_DIRS environment variable is replaced by a non-existent folder.\n"
485 "Unfortunately, indentation files are not accessible with indentFiles(), nor are syntax files, according to the KSyntaxHighlighting compilation "
487 "This option cancels the value replacement.")},
489 {u
"X"_s, tr(
"force a value for XDG_DATA_DIRS and ignore -x."), tr(
"path")},
491 {{u
"S"_s, u
"set-variable"_s},
492 tr(
"Set document variables before running a test file. This is equivalent to `document.setVariable(key, value)` at the start of the file. Call this "
493 "option several times to set multiple parameters."),
499 {u
"no-color"_s, tr(
"No color on the output")},
501 {u
"color-reset"_s, tr(
"Sequence to reset color and style."), translatedColors},
502 {u
"color-success"_s, tr(
"Color for success."), translatedColors},
503 {u
"color-error"_s, tr(
"Color for error or exception."), translatedColors},
504 {u
"color-carret"_s, tr(
"Color for '^~~' under error position."), translatedColors},
505 {u
"color-debug-marker"_s, tr(
"Color for 'DEBUG:' and 'PRINT:' prefixes inserted with debug(), print() and printSep()."), translatedColors},
506 {u
"color-debug-message"_s, tr(
"Color for message with debug()."), translatedColors},
507 {u
"color-test-name"_s, tr(
"Color for name of the test."), translatedColors},
508 {u
"color-program"_s, tr(
"Color for program paramater in cmd() / test() and function name in stacktrace."), translatedColors},
509 {u
"color-file"_s, tr(
"Color for file name."), translatedColors},
510 {u
"color-line"_s, tr(
"Color for line number."), translatedColors},
511 {u
"color-block-selection-info"_s, tr(
"Color for [blockSelection=...] in a check."), translatedColors},
512 {u
"color-label-info"_s, tr(
"Color for 'input', 'output', 'result' label when it is displayed as information and not as an error."), translatedColors},
513 {u
"color-cursor"_s, tr(
"Color for cursor placeholder."), translatedColors},
514 {u
"color-selection"_s, tr(
"Color for selection placeholder."), translatedColors},
515 {u
"color-secondary-cursor"_s, tr(
"Color for secondary cursor placeholder."), translatedColors},
516 {u
"color-secondary-selection"_s, tr(
"Color for secondary selection placeholder."), translatedColors},
517 {u
"color-block-selection"_s, tr(
"Color for block selection placeholder."), translatedColors},
518 {u
"color-in-selection"_s, tr(
"Style added for text inside a selection."), translatedColors},
519 {u
"color-virtual-text"_s, tr(
"Color for virtual text placeholder."), translatedColors},
520 {u
"color-replacement"_s, tr(
"Color for text replaced by --format=placeholder."), translatedColors},
521 {u
"color-text-result"_s, tr(
"Color for text representing the inputs and outputs."), translatedColors},
523 tr(
"Color added to all colors used to display a result:\n"
525 "--color-selection\n"
526 "--color-secondary-cursor\n"
527 "--color-secondary-selection\n"
528 "--color-block-selection\n"
529 "--color-virtual-text\n"
530 "--color-replacement\n"
531 "--color-text-result."),
537struct CommandLineParseResult {
544 Status statusCode = Status::Ok;
545 QString errorString = {};
548CommandLineParseResult parseCommandLine(
QCommandLineParser &parser, ScriptTesterQuery *query)
550 using Status = CommandLineParseResult::Status;
556 return {Status::Error, parser.
errorText()};
558 if (parser.
isSet(u
"v"_s))
559 return {Status::VersionRequested};
561 if (parser.
isSet(u
"h"_s))
562 return {Status::HelpRequested};
566 if (parser.
isSet(u
"q"_s)) {
567 query->executionConfig.maxError = 1;
569 if (parser.
isSet(u
"e"_s)) {
573 return {Status::Error, u
"--max-error: invalid number"_s};
576 query->executionConfig.xCheckAsFailure = parser.
isSet(u
"E"_s);
578 if (parser.
isSet(u
"s"_s)) {
584 const auto paths = parser.
values(u
"s"_s);
585 for (
const auto &path : paths) {
586 addPath(
query->paths.scripts, path + u
"/command"_sv);
587 addPath(
query->paths.scripts, path + u
"/indentation"_sv);
588 addPath(
query->paths.libraries, path + u
"/library"_sv);
589 addPath(
query->paths.files, path + u
"/files"_sv);
594 if (parser.
isSet(opt)) {
595 const auto paths = parser.
values(opt);
596 for (
const auto &path : paths) {
602 setPaths(
query->paths.scripts, u
"c"_s);
603 setPaths(
query->paths.libraries, u
"l"_s);
604 setPaths(
query->paths.files, u
"r"_s);
605 setPaths(
query->paths.modules, u
"m"_s);
606 query->paths.indentBaseDir = parser.
value(u
"I"_s);
608 if (parser.
isSet(u
"d"_s)) {
609 const auto value = parser.
value(u
"d"_s);
610 if (value == u
"location"_sv) {
611 query->format.debugOptions |= DebugOption::WriteLocation;
612 }
else if (value == u
"function"_sv) {
613 query->format.debugOptions |= DebugOption::WriteFunction;
614 }
else if (value == u
"stacktrace"_sv) {
615 query->format.debugOptions |= DebugOption::WriteStackTrace;
616 }
else if (value == u
"flush"_sv) {
617 query->format.debugOptions |= DebugOption::ForceFlush;
618 }
else if (value == u
"extended"_sv) {
619 query->extendedDebug =
true;
620 }
else if (value == u
"no-location"_sv) {
621 query->format.debugOptions.setFlag(DebugOption::WriteLocation,
false);
622 }
else if (value == u
"no-function"_sv) {
623 query->format.debugOptions.setFlag(DebugOption::WriteFunction,
false);
624 }
else if (value == u
"no-stacktrace"_sv) {
625 query->format.debugOptions.setFlag(DebugOption::WriteStackTrace,
false);
626 }
else if (value == u
"no-flush"_sv) {
627 query->format.debugOptions.setFlag(DebugOption::ForceFlush,
false);
628 }
else if (value == u
"no-extended"_sv) {
629 query->extendedDebug =
false;
630 }
else if (value == u
"all"_sv) {
631 query->extendedDebug =
true;
632 query->format.debugOptions = DebugOption::WriteLocation | DebugOption::WriteFunction | DebugOption::WriteStackTrace | DebugOption::ForceFlush;
633 }
else if (value == u
"none"_sv) {
634 query->extendedDebug =
false;
635 query->format.debugOptions = {};
637 return {Status::Error, u
"--debug: invalid value"_s};
641 if (parser.
isSet(u
"H"_s)) {
642 query->format.testFormatOptions |= TestFormatOption::HiddenTestName;
644 if (parser.
isSet(u
"p"_s)) {
645 query->format.testFormatOptions |= TestFormatOption::AlwaysWriteLocation;
647 if (parser.
isSet(u
"V"_s)) {
648 query->format.testFormatOptions |= TestFormatOption::AlwaysWriteInputOutput;
652 if (parser.
isSet(opt)) {
653 const auto value = parser.
value(opt);
654 if (value == u
"raw"_sv) {
655 textFormat = TextFormat::Raw;
656 }
else if (value == u
"js"_sv) {
657 textFormat = TextFormat::EscapeForDoubleQuote;
658 }
else if (value == u
"placeholder"_sv) {
659 textFormat = TextFormat::ReplaceNewLineAndTabWithPlaceholder;
660 }
else if (value == u
"placeholder2"_sv) {
661 textFormat = TextFormat::ReplaceTabWithPlaceholder;
662 }
else if (value == u
"literal"_sv) {
663 textFormat = TextFormat::ReplaceNewLineAndTabWithLiteral;
670 if (!setFormat(
query->format.documentTextFormat, u
"f"_s)) {
671 return {Status::Error, u
"--format: invalid value"_s};
673 if (!setFormat(
query->format.documentTextFormatWithBlockSelection, u
"F"_s)) {
674 return {Status::Error, u
"--block-format: invalid value"_s};
677 auto setPattern = [&parser, &
query](
QString opt, PatternType patternType) {
678 if (parser.
isSet(opt)) {
680 query->executionConfig.pattern.setPattern(parser.
value(opt));
681 if (!
query->executionConfig.pattern.isValid()) {
684 query->executionConfig.patternType = patternType;
689 if (!setPattern(u
"k"_s, PatternType::Include)) {
690 return {Status::Error, u
"-k: "_sv +
query->executionConfig.pattern.errorString()};
692 if (!setPattern(u
"K"_s, PatternType::Exclude)) {
693 return {Status::Error, u
"-K: "_sv +
query->executionConfig.pattern.errorString()};
696 if (parser.
isSet(u
"T"_s)) {
697 const auto tab = parser.
value(u
"T"_s);
698 if (tab.size() == 0) {
699 query->format.textReplacement.tab1 = defaultTextReplacement.tab1;
700 query->format.textReplacement.tab2 = defaultTextReplacement.tab2;
702 query->format.textReplacement.tab1 = tab[0];
703 query->format.textReplacement.tab2 = (tab.size() == 1) ?
query->format.textReplacement.tab1 : tab[1];
708 return str.
size() > i ? str[i] : c;
711 if (parser.
isSet(u
"N"_s)) {
712 const auto nl = parser.
value(u
"N"_s);
713 query->format.textReplacement.newLine = getChar(nl, 0,
query->format.textReplacement.newLine);
716 if (parser.
isSet(u
"P"_s)) {
717 const auto symbols = parser.
value(u
"P"_s);
718 auto &ph =
query->format.fallbackPlaceholders;
719 ph.cursor = getChar(symbols, 0, defaultFallbackPlaceholders.cursor);
720 ph.selectionStart = getChar(symbols, 1, defaultFallbackPlaceholders.selectionStart);
721 ph.selectionEnd = getChar(symbols, 2, defaultFallbackPlaceholders.selectionEnd);
722 ph.secondaryCursor = getChar(symbols, 3, defaultFallbackPlaceholders.secondaryCursor);
723 ph.secondarySelectionStart = getChar(symbols, 4, defaultFallbackPlaceholders.secondarySelectionStart);
724 ph.secondarySelectionEnd = getChar(symbols, 5, defaultFallbackPlaceholders.secondarySelectionEnd);
725 ph.virtualText = getChar(symbols, 6, defaultFallbackPlaceholders.virtualText);
728 if (parser.
isSet(u
"B"_s)) {
729 query->dualMode = DualMode::NoBlockSelection;
732 if (parser.
isSet(u
"b"_s)) {
733 const auto mode = parser.
value(u
"b"_s);
734 if (mode == u
"noblock"_sv) {
735 query->dualMode = DualMode::NoBlockSelection;
736 }
else if (mode == u
"block"_sv) {
737 query->dualMode = DualMode::BlockSelection;
738 }
else if (mode == u
"always-dual"_sv) {
739 query->dualMode = DualMode::DualIsAlwaysDual;
740 }
else if (mode == u
"no-always-dual"_sv) {
741 query->dualMode = DualMode::AlwaysDualIsDual;
742 }
else if (mode == u
"dual"_sv) {
743 query->dualMode = DualMode::Dual;
745 return {Status::Error, u
"--dual: invalid value"_s};
747 query->dualMode = DualMode::NoBlockSelection;
752 if (parser.
isSet(u
"preamble"_s)) {
753 query->preamble = parser.
value(u
"preamble"_s);
756 query->showPreamble = parser.
isSet(u
"print-preamble"_s);
758 if (parser.
isSet(u
"X"_s)) {
760 query->restoreXdgDataDirs =
true;
762 query->restoreXdgDataDirs = parser.
isSet(u
"x"_s);
765 if (parser.
isSet(u
"S"_s)) {
766 const auto variables = parser.
values(u
"S"_s);
769 for (
const auto &kv : variables) {
772 it->key = kv.sliced(0, pos);
773 it->value = kv.sliced(pos + 1);
781 query->diff.path = parser.
isSet(u
"D"_s) ? parser.
value(u
"D"_s) : u
"diff"_s;
783 const bool noColor = parser.
isSet(u
"no-color"_s);
785 if (parser.
isSet(u
"A"_s)) {
806 query->format.colors.blockSelectionInfo.
clear();
810 query->format.colors.secondarySelection.
clear();
815 query->format.colors.resultReplacement.
clear();
820 if (parser.
isSet(opt)) {
822 color = toANSIColor(parser.
value(opt), defaultResultColor, &ok);
831 setColor(
query->format.colors.reset, u
"color-reset"_s);
832 setColor(
query->format.colors.success, u
"color-success"_s);
833 setColor(
query->format.colors.error, u
"color-error"_s);
834 setColor(
query->format.colors.carret, u
"color-carret"_s);
835 setColor(
query->format.colors.debugMarker, u
"color-debug-marker"_s);
836 setColor(
query->format.colors.debugMsg, u
"color-debug-message"_s);
837 setColor(
query->format.colors.testName, u
"color-test-name"_s);
838 setColor(
query->format.colors.program, u
"color-program"_s);
839 setColor(
query->format.colors.fileName, u
"color-file"_s);
840 setColor(
query->format.colors.lineNumber, u
"color-line"_s);
841 setColor(
query->format.colors.labelInfo, u
"color-label-info"_s);
842 setColor(
query->format.colors.blockSelectionInfo, u
"color-block-selection-info"_s);
843 setColor(
query->format.colors.inSelection, u
"color-in-selection"_s);
845 if (!setColor(defaultResultColor, u
"color-result"_s)) {
846 defaultResultColor = u
"\x1b[40m"_s;
848 const bool hasDefault = defaultResultColor.
size();
850 if (!setColor(
query->format.colors.cursor, u
"color-cursor"_s) && hasDefault) {
851 query->format.colors.cursor = ansiBg % u
";1;33m"_sv;
853 if (!setColor(
query->format.colors.selection, u
"color-selection"_s) && hasDefault) {
854 query->format.colors.selection = ansiBg % u
";1;33m"_sv;
856 if (!setColor(
query->format.colors.secondaryCursor, u
"color-secondary-cursor"_s) && hasDefault) {
857 query->format.colors.secondaryCursor = ansiBg % u
";33m"_sv;
859 if (!setColor(
query->format.colors.secondarySelection, u
"color-secondary-selection"_s) && hasDefault) {
860 query->format.colors.secondarySelection = ansiBg % u
";33m"_sv;
862 if (!setColor(
query->format.colors.blockSelection, u
"color-block-selection"_s) && hasDefault) {
863 query->format.colors.blockSelection = ansiBg % u
";37m"_sv;
865 if (!setColor(
query->format.colors.virtualText, u
"color-virtual-text"_s) && hasDefault) {
866 query->format.colors.virtualText = ansiBg % u
";37m"_sv;
868 if (!setColor(
query->format.colors.result, u
"color-text-result"_s) && hasDefault) {
869 query->format.colors.result = defaultResultColor;
871 if (!setColor(
query->format.colors.resultReplacement, u
"color-replacement"_s) && hasDefault) {
872 query->format.colors.resultReplacement = ansiBg % u
";36m"_sv;
876 return {Status::Error, u
"--"_sv % optWithError % u
": invalid color"_sv};
885void addTextStyleProperties(
QJSValue &obj)
889 obj.
setProperty(u
"dsKeyword"_s, TextStyle::Keyword);
890 obj.
setProperty(u
"dsFunction"_s, TextStyle::Function);
891 obj.
setProperty(u
"dsVariable"_s, TextStyle::Variable);
892 obj.
setProperty(u
"dsControlFlow"_s, TextStyle::ControlFlow);
893 obj.
setProperty(u
"dsOperator"_s, TextStyle::Operator);
894 obj.
setProperty(u
"dsBuiltIn"_s, TextStyle::BuiltIn);
895 obj.
setProperty(u
"dsExtension"_s, TextStyle::Extension);
896 obj.
setProperty(u
"dsPreprocessor"_s, TextStyle::Preprocessor);
897 obj.
setProperty(u
"dsAttribute"_s, TextStyle::Attribute);
899 obj.
setProperty(u
"dsSpecialChar"_s, TextStyle::SpecialChar);
901 obj.
setProperty(u
"dsVerbatimString"_s, TextStyle::VerbatimString);
902 obj.
setProperty(u
"dsSpecialString"_s, TextStyle::SpecialString);
904 obj.
setProperty(u
"dsDataType"_s, TextStyle::DataType);
908 obj.
setProperty(u
"dsConstant"_s, TextStyle::Constant);
909 obj.
setProperty(u
"dsComment"_s, TextStyle::Comment);
910 obj.
setProperty(u
"dsDocumentation"_s, TextStyle::Documentation);
911 obj.
setProperty(u
"dsAnnotation"_s, TextStyle::Annotation);
912 obj.
setProperty(u
"dsCommentVar"_s, TextStyle::CommentVar);
913 obj.
setProperty(u
"dsRegionMarker"_s, TextStyle::RegionMarker);
914 obj.
setProperty(u
"dsInformation"_s, TextStyle::Information);
915 obj.
setProperty(u
"dsWarning"_s, TextStyle::Warning);
924static qsizetype timeNowInMs()
926 auto t = std::chrono::high_resolution_clock::now().time_since_epoch();
927 return std::chrono::duration_cast<std::chrono::milliseconds>(t).count();
930QtMessageHandler originalHandler =
nullptr;
936 if (originalHandler && context.category != std::string_view(
"kf.sonnet.core")) {
937 originalHandler(type, context, msg);
943int main(
int ac,
char **av)
945 ScriptTesterQuery
query;
946 query.xdgDataDirs = qgetenv(
"XDG_DATA_DIRS");
948 qputenv(
"QT_QPA_PLATFORM",
"offscreen");
953 qputenv(
"XDG_DATA_DIRS",
"/XDG_DATA_DIRS_unknown_folder");
955 originalHandler = qInstallMessageHandler(filterMessageOutput);
972 initCommandLineParser(app, parser);
974 using Status = CommandLineParseResult::Status;
975 CommandLineParseResult parseResult = parseCommandLine(parser, &query);
976 switch (parseResult.statusCode) {
979 std::fputs(
"No test file specified.\nUse -h / --help for more details.\n", stderr);
984 std::fputs(qPrintable(parseResult.errorString), stderr);
985 std::fputs(
"\nUse -h / --help for more details.\n", stderr);
987 case Status::VersionRequested:
990 case Status::HelpRequested:
991 std::fputs(qPrintable(parser.
helpText()), stdout);
994 Comma-separated list of values:
995 - color name: black, green, yellow, blue, magenta, cyan, white
996 - bright color name: bright-${color name}
997 - rgb: #fff or #ffffff (use trueColor sequence)
998 - background color: bg=${color name} or bg=bright-${color name} bg=${rgb}
999 - style: bold, dim, italic, underline, reverse, strike, doubly-underline, overlined
1000 - ANSI sequence: number sequence with optional ';'
1011 auto jsInjectionStart1 =
1012 u
"(function(env, argv){"
1013 u
"const TestFramework = this.loadModule(':/ktexteditor/scripttester/testframework.js');"
1014 u
"const {REUSE_LAST_INPUT, REUSE_LAST_EXPECTED_OUTPUT} = TestFramework;"
1015 u
"const AS_INPUT = TestFramework.EXPECTED_OUTPUT_AS_INPUT;"
1016 u
"var {calleeWrapper, config, print, printSep, testCase, sequence, withInput, keys,"
1017 u
" indentFiles, test, xtest, eqvTrue, eqvFalse, eqTrue, eqFalse, error, errorMsg,"
1018 u
" errorType, hasError, eqv, is, eq, ne, lt, gt, le, ge, cmd, xcmd, type, xtype"
1019 u
" } = TestFramework;"
1020 u
"var c = TestFramework.sanitizeTag;"
1021 u
"var lazyfn = (fn, ...args) => new TestFramework.LazyFunc(fn, ...args);"
1023 u
"var lazyarg = (arg) => new TestFramework.LazyArg(arg);"
1024 u
"var arg = lazyarg;"
1025 u
"var loadScript = this.loadScript;"
1026 u
"var loadModule = this.loadModule;"
1027 u
"var paste = (str) => this.paste(str);"
1028 u
"env.editor = TestFramework.editor;"
1029 u
"var document = calleeWrapper('document', env.document);"
1030 u
"var editor = calleeWrapper('editor', env.editor);"
1031 u
"var view = calleeWrapper('view', env.view);"
1033 auto debugSetup =
query.extendedDebug ? u
"debug = testFramework.debug;"_sv : u
""_sv;
1035 auto dualModeSetup =
query.dualMode == DualMode::Dual
1036 ? u
"const DUAL_MODE = TestFramework.DUAL_MODE;"
1037 u
"const ALWAYS_DUAL_MODE = TestFramework.ALWAYS_DUAL_MODE;"_sv
1038 :
query.dualMode == DualMode::NoBlockSelection
1039 ? u
"const DUAL_MODE = 0;"
1040 u
"const ALWAYS_DUAL_MODE = 0;"_sv
1041 :
query.dualMode == DualMode::BlockSelection
1042 ? u
"const DUAL_MODE = 1;"
1043 u
"const ALWAYS_DUAL_MODE = 1;"_sv
1044 :
query.dualMode == DualMode::DualIsAlwaysDual
1045 ? u
"const DUAL_MODE = TestFramework.ALWAYS_DUAL_MODE;"
1046 u
"const ALWAYS_DUAL_MODE = TestFramework.ALWAYS_DUAL_MODE;"_sv
1048 : u
"const DUAL_MODE = TestFramework.DUAL_MODE;"
1049 u
"const ALWAYS_DUAL_MODE = TestFramework.DUAL_MODE;"_sv;
1051 auto jsInjectionStart2 =
1052 u
"var kbd = TestFramework.init(this, env, DUAL_MODE);"
1053 u
"try { void function(){"_sv;
1054 auto jsInjectionEnd =
1057 u
"if (e !== TestFramework.STOP_CASE_ERROR) {"
1065 const auto pattern = u
"{CODE}"_sv;
1067 auto pos = preamble.
indexOf(pattern);
1069 std::fputs(
"missing {CODE} with --preamble\n", stderr);
1072 jsInjectionStart1 = preamble.
sliced(0, pos);
1073 jsInjectionEnd = preamble.
sliced(pos + pattern.size());
1080 return jsInjectionStart1 % debugSetup % dualModeSetup % jsInjectionStart2 % u
'\n' % source % jsInjectionEnd;
1083 if (
query.showPreamble) {
1084 std::fputs(qPrintable(makeProgram(u
"{CODE}"_sv)), stdout);
1088 if (
query.restoreXdgDataDirs) {
1089 qputenv(
"XDG_DATA_DIRS",
query.xdgDataDirs);
1097 KTextEditor::ViewPrivate view(&doc,
nullptr);
1102 viewObj.setView(&view);
1105 docObj.setDocument(&doc);
1113 ScriptTester scriptTester(&output,
query.format,
query.paths,
query.executionConfig,
query.diff, defaultPlaceholder, &engine, &doc, &view);
1130 addTextStyleProperties(globalObject);
1134 scriptTester.require(u
"range.js"_s);
1138 "function i18n(text, ...arg) { return text; }\n"
1139 "function i18nc(context, text, ...arg) { return text; }\n"
1140 "function i18np(singular, plural, number, ...arg) { return number > 1 ? plural : singular; }\n"
1141 "function i18ncp(context, singular, plural, number, ...arg) { return number > 1 ? plural : singular; }\n"
1143 "var editor = undefined;"));
1150 for (quint32 i = 0; i <
query.argv.
size(); ++i) {
1154 const auto &colors =
query.format.colors;
1156 qsizetype delayInMs = 0;
1157 bool resetConfig =
false;
1158 auto runProgram = [&](
const QString &fileName,
const QString &source) {
1159 auto result = engine.
evaluate(makeProgram(source), fileName, 0);
1160 if (!result.isError()) {
1162 scriptTester.resetConfig();
1166 for (
const auto &variable : std::as_const(
query.variables)) {
1167 doc.setVariable(variable.key, variable.value);
1170 const auto start = timeNowInMs();
1171 result = result.callWithInstance(functions, {globalObject, jsArgv});
1172 delayInMs += timeNowInMs() -
start;
1173 if (!result.isError()) {
1178 scriptTester.incrementError();
1179 scriptTester.stream() << colors.error << result.toString() << colors.reset << u
'\n';
1180 scriptTester.writeException(result, u
"| "_sv);
1181 scriptTester.stream().flush();
1185 auto runJsFile = [&](
const QString &fileName) {
1191 scriptTester.incrementError();
1192 scriptTester.stream() << colors.fileName << fileName << colors.reset <<
": "_L1 << colors.error << file.
errorString() << colors.reset << u
'\n';
1193 scriptTester.stream().flush();
1198 runProgram(fileName, content);
1207 for (
const auto &fileName : fileNames) {
1209 runProgram(u
"file%1.js"_s.arg(&fileName -
fileNames.
data() + 1), fileName);
1210 }
else if (!
QFileInfo(fileName).isDir()) {
1211 runJsFile(fileName);
1214 while (it.hasNext() && !scriptTester.hasTooManyErrors()) {
1215 runJsFile(it.next());
1219 if (scriptTester.hasTooManyErrors()) {
1228 if (scriptTester.hasTooManyErrors()) {
1229 scriptTester.stream() << colors.error <<
"Too many error"_L1 << colors.reset << u
'\n';
1232 scriptTester.writeSummary();
1233 scriptTester.stream() <<
" Duration: "_L1 << delayInMs <<
"ms\n"_L1;
1234 scriptTester.stream().flush();
1236 return scriptTester.countError() ? 1 : 0;
Backend of KTextEditor::Document related public KTextEditor interfaces.
Thinish wrapping around KTextEditor::DocumentPrivate, exposing the methods we want exposed and adding...
Thinish wrapping around KTextEditor::ViewPrivate, exposing the methods we want exposed and adding som...
Q_SCRIPTABLE QString start(QString train="")
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QString path(const QString &relativePath)
KEDUVOCDOCUMENT_EXPORT QStringList fileNames(const QString &language=QString())
QCommandLineOption addHelpOption()
bool addOptions(const QList< QCommandLineOption > &options)
void addPositionalArgument(const QString &name, const QString &description, const QString &syntax)
QCommandLineOption addVersionOption()
QString errorText() const const
QString helpText() const const
bool isSet(const QCommandLineOption &option) const const
bool parse(const QStringList &arguments)
QStringList positionalArguments() const const
void setApplicationDescription(const QString &description)
QString value(const QCommandLineOption &option) const const
QStringList values(const QCommandLineOption &option) const const
void setApplicationName(const QString &application)
void setApplicationVersion(const QString &version)
void setOrganizationDomain(const QString &orgDomain)
void setOrganizationName(const QString &orgName)
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
bool exists(const QString &fileName)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
void setFileName(const QString &name)
virtual void close() override
FileError error() const const
QString errorString() const const
QJSValue evaluate(const QString &program, const QString &fileName, int lineNumber, QStringList *exceptionStackTrace)
QJSValue globalObject() const const
QJSValue newArray(uint length)
QJSValue newQObject(QObject *object)
QJSValue property(const QString &name) const const
void setProperty(const QString &name, const QJSValue &value)
void append(QList< T > &&value)
const_pointer constData() const const
bool isEmpty() const const
void push_back(parameter_type value)
void resize(qsizetype size)
qsizetype size() const const
void setTestModeEnabled(bool testMode)
const QChar * constData() const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
qsizetype indexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype size() const const
QStringView sliced(qsizetype pos) const const
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
const T * constData() const const