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>
34constexpr QStringView operator""_sv(
const char16_t *str,
size_t size)
noexcept
39using ScriptTester = KTextEditor::ScriptTester;
40using TextFormat = ScriptTester::DocumentTextFormat;
41using TestFormatOption = ScriptTester::TestFormatOption;
42using PatternType = ScriptTester::PatternType;
43using DebugOption = ScriptTester::DebugOption;
45constexpr inline ScriptTester::Format::TextReplacement defaultTextReplacement{
50constexpr inline ScriptTester::Placeholders defaultPlaceholder{
52 .selectionStart = u
'[',
54 .secondaryCursor = u
'\0',
55 .secondarySelectionStart = u
'\0',
56 .secondarySelectionEnd = u
'\0',
59constexpr inline ScriptTester::Placeholders defaultFallbackPlaceholders{
61 .selectionStart = u
'[',
63 .secondaryCursor = u
'┆',
64 .secondarySelectionStart = u
'❲',
65 .secondarySelectionEnd = u
'❳',
77struct ScriptTesterQuery {
78 ScriptTester::Format format{.debugOptions = DebugOption::WriteLocation | DebugOption::WriteFunction,
79 .testFormatOptions = TestFormatOption::None,
80 .documentTextFormat = TextFormat::ReplaceNewLineAndTabWithLiteral,
81 .documentTextFormatWithBlockSelection = TextFormat::ReplaceNewLineAndTabWithPlaceholder,
82 .textReplacement = defaultTextReplacement,
83 .fallbackPlaceholders = defaultFallbackPlaceholders,
86 .success = u
"\033[32m"_s,
87 .error = u
"\033[31m"_s,
88 .carret = u
"\033[31m"_s,
89 .debugMarker = u
"\033[31;1m"_s,
90 .debugMsg = u
"\033[31m"_s,
91 .testName = u
"\x1b[36m"_s,
92 .program = u
"\033[32m"_s,
93 .fileName = u
"\x1b[34m"_s,
94 .lineNumber = u
"\x1b[35m"_s,
95 .blockSelectionInfo = u
"\x1b[37m"_s,
96 .labelInfo = u
"\x1b[37m"_s,
97 .cursor = u
"\x1b[40;1;33m"_s,
98 .selection = u
"\x1b[40;1;33m"_s,
99 .secondaryCursor = u
"\x1b[40;33m"_s,
100 .secondarySelection = u
"\x1b[40;33m"_s,
101 .blockSelection = u
"\x1b[40;37m"_s,
102 .inSelection = u
"\x1b[4m"_s,
103 .virtualText = u
"\x1b[40;37m"_s,
104 .result = u
"\x1b[40m"_s,
105 .resultReplacement = u
"\x1b[40;36m"_s,
108 ScriptTester::Paths paths{
110 .libraries = {u
":/ktexteditor/script/libraries"_s},
116 ScriptTester::TestExecutionConfig executionConfig;
118 ScriptTester::DiffCommand diff;
134 DualMode dualMode = DualMode::Dual;
136 bool showPreamble =
false;
137 bool extendedDebug =
false;
138 bool restoreXdgDataDirs =
false;
150 static TrueColor fromRGB(
QStringView color,
bool isBg)
152 auto toHex = [](
QChar c) {
153 if (c <= u
'9' && c >= u
'0') {
154 return c.unicode() - u
'0';
156 if (c <= u
'f' && c >= u
'a') {
157 return c.unicode() - u
'a';
159 if (c <= u
'F' && c >= u
'A') {
160 return c.unicode() - u
'A';
171 if (color.
size() == 4) {
180 else if (color.
size() == 7) {
181 r = (toHex(color[1]) << 4) + toHex(color[2]);
182 g = (toHex(color[3]) << 4) + toHex(color[4]);
183 b = (toHex(color[5]) << 4) + toHex(color[6]);
191 auto *p = trueColor.ansi;
192 auto pushComponent = [&](
int color) {
194 *p++ =
"0123456789"[color / 100];
196 *p++ =
"0123456789"[color / 10];
197 }
else if (color > 9) {
198 *p++ =
"0123456789"[color / 10];
200 *p++ =
"0123456789"[color % 10];
207 trueColor.len = p - trueColor.ansi;
208 trueColor.isBg = isBg;
232 qsizetype totalLen = 0;
233 bool hasDefaultColor = !defaultColor.
isEmpty();
235 if (colors.isEmpty()) {
236 result = defaultColor;
244 for (
auto &color : colors) {
246 if (color[0] <= u
'9' && color[0] >= u
'0') {
248 for (
auto c : color) {
249 if (c > u
'9' && c < u
'0' && c != u
';') {
255 const bool isBg = color.startsWith(u
"bg=");
256 auto s = isBg ? color.sliced(3) : color;
257 const bool isBright = s.startsWith(u
"bright-");
258 s = isBright ? s.sliced(7) : s;
267 const auto trueColor = TrueColor::fromRGB(s, isBg);
268 if (!trueColor.len) {
272 totalLen += 5 + trueColor.len;
273 trueColors += trueColor;
276 }
else if (s == u
"black"_sv) {
277 color = SVs{u
"30"_sv, u
"40"_sv, u
"90"_sv, u
"100"_sv}[isBg + isBright * 2];
278 }
else if (s == u
"red"_sv) {
279 color = SVs{u
"31"_sv, u
"41"_sv, u
"91"_sv, u
"101"_sv}[isBg + isBright * 2];
280 }
else if (s == u
"green"_sv) {
281 color = SVs{u
"32"_sv, u
"42"_sv, u
"92"_sv, u
"102"_sv}[isBg + isBright * 2];
282 }
else if (s == u
"yellow"_sv) {
283 color = SVs{u
"33"_sv, u
"43"_sv, u
"93"_sv, u
"103"_sv}[isBg + isBright * 2];
284 }
else if (s == u
"blue"_sv) {
285 color = SVs{u
"34"_sv, u
"44"_sv, u
"94"_sv, u
"104"_sv}[isBg + isBright * 2];
286 }
else if (s == u
"magenta"_sv) {
287 color = SVs{u
"35"_sv, u
"45"_sv, u
"95"_sv, u
"105"_sv}[isBg + isBright * 2];
288 }
else if (s == u
"cyan"_sv) {
289 color = SVs{u
"36"_sv, u
"46"_sv, u
"96"_sv, u
"106"_sv}[isBg + isBright * 2];
290 }
else if (s == u
"white"_sv) {
291 color = SVs{u
"37"_sv, u
"47"_sv, u
"97"_sv, u
"107"_sv}[isBg + isBright * 2];
293 }
else if (!isBg && !isBright && s == u
"bold"_sv) {
295 }
else if (!isBg && !isBright && s == u
"dim"_sv) {
297 }
else if (!isBg && !isBright && s == u
"italic"_sv) {
299 }
else if (!isBg && !isBright && s == u
"underline"_sv) {
301 }
else if (!isBg && !isBright && s == u
"reverse"_sv) {
303 }
else if (!isBg && !isBright && s == u
"strike"_sv) {
305 }
else if (!isBg && !isBright && s == u
"doubly-underlined"_sv) {
307 }
else if (!isBg && !isBright && s == u
"overlined"_sv) {
316 totalLen += color.size() + 1;
319 if (hasDefaultColor) {
320 totalLen += defaultColor.
size() - 2;
325 if (!hasDefaultColor) {
326 result += u
"\x1b["_sv;
328 result += defaultColor;
329 result.
back() = u
';';
335 auto const *trueColorIt = trueColors.
constData();
336 for (
const auto &color : std::as_const(colors)) {
337 if (!color.isEmpty()) {
340 result += trueColorIt->isBg ? u
"48;2;"_sv : u
"38;2;"_sv;
341 result += trueColorIt->sv();
347 result.
back() = u
'm';
349 result = defaultColor;
357 auto tr = [&app](
char const *s) {
358 return app.
translate(
"KateScriptTester", s);
361 const auto translatedFolder = tr(
"folder");
362 const auto translatedOption = tr(
"option");
363 const auto translatedPattern = tr(
"pattern");
364 const auto translatedPlaceholder = tr(
"character");
365 const auto translatedColors = tr(
"colors");
368 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..."));
373 {{u
"t"_s, u
"text"_s}, tr(
"Files are treated as javascript code rather than file names.")},
378 {{u
"e"_s, u
"max-error"_s}, tr(
"Maximum number of tests that can fail before stopping.")},
379 {u
"q"_s, tr(
"Alias of --max-error=1.")},
380 {{u
"E"_s, u
"expected-failure-as-failure"_s}, tr(
"functions xcmd() and xtest() will always fail.")},
385 {{u
"s"_s, u
"script"_s},
386 tr(
"Shorcut for --command=${script}/commands --command=${script}/indentation --library=${script}/library --file=${script}/files."),
388 {{u
"c"_s, u
"command"_s}, tr(
"Adds a search folder for loadScript()."), translatedFolder},
389 {{u
"l"_s, u
"library"_s}, tr(
"Adds a search folder for require() (KTextEditor JS API)."), translatedFolder},
390 {{u
"r"_s, u
"file"_s}, tr(
"Adds a search folder for read() (KTextEditor JS API)."), translatedFolder},
391 {{u
"m"_s, u
"module"_s}, tr(
"Adds a search folder for loadModule()."), translatedFolder},
392 {{u
"I"_s, u
"indent-data-test"_s}, tr(
"Set indentation base directory for indentFiles()."), translatedFolder},
397 {{u
"D"_s, u
"diff-path"_s}, tr(
"Path of diff command."), tr(
"path")},
398 {{u
"A"_s, u
"diff-arg"_s}, tr(
"Argument for diff command. Call this option several times to set multiple parameters."), translatedOption},
403 {{u
"d"_s, u
"debug"_s},
404 tr(
"Concerning the display of the debug() function. Can be used multiple times to change multiple options.\n"
405 "- location: displays the file and line number of the call (enabled by default)\n"
406 "- function: displays the name of the function that uses debug() (enabled by default)\n"
407 "- stacktrace: show the call stack after the debug message\n"
408 "- flush: debug messages are normally buffered and only displayed in case of error. This option removes buffering\n"
409 "- extended: debug() can take several parameters of various types such as Array or Object. This behavior is specific and should not be exploited "
411 "- no-location: inverse of location\n"
412 "- no-function: inverse of function\n"
413 "- no-stacktrace: inverse of stacktrace\n"
414 "- no-flush: inverse of flush\n"
415 "- all: enable all\n"
416 "- none: disable all"),
418 {{u
"H"_s, u
"hidden-name"_s}, tr(
"Do not display test names.")},
419 {{u
"p"_s, u
"parade"_s}, tr(
"Displays all tests run or skipped. By default, only error tests are displayed.")},
420 {{u
"V"_s, u
"verbose"_s}, tr(
"Displays input and ouput on each tests. By default, only error tests are displayed.")},
421 {{u
"f"_s, u
"format"_s},
422 tr(
"Defines the document text display format:\n"
423 "- raw: no transformation\n"
424 "- js: display in literal string in javascript format\n"
425 "- literal: replaces new lines and tabs with \\n and \\t (default)\n"
426 "- placeholder: replaces new lines and tabs with placeholders specified by --newline and --tab\n"
427 "- placeholder2: replaces tabs with the placeholder specified by --tab\n"),
429 {{u
"F"_s, u
"block-format"_s}, tr(
"Same as --format, but with block selection text."), translatedOption},
434 {{u
"k"_s, u
"filter"_s}, tr(
"Only runs tests whose name matches a regular expression."), translatedPattern},
435 {u
"K"_s, tr(
"Only runs tests whose name does not matches a regular expression."), translatedPattern},
441 tr(
"Character used to replace a tab in the test display with --format=placeholder. If 2 characters are given, the second corresponds the last "
442 "character replaced. --tab='->' with tabWith=4 gives '--->'."),
443 translatedPlaceholder},
444 {{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},
445 {{u
"P"_s, u
"placeholders"_s},
446 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. "
449 "- selection start\n"
451 "- secondary cursor\n"
452 "- secondary selection start\n"
453 "- secondary selection end\n"
460 {{u
"b"_s, u
"dual"_s},
461 tr(
"Change DUAL_MODE and ALWAYS_DUAL_MODE constants behavior:\n"
462 "- noblock: never block selection (equivalent to setConfig({blockSelection=0}))\n"
463 "- block: always block selection (equivalent to setConfig({blockSelection=1}))\n"
464 "- always-dual: DUAL_MODE = ALWAYS_DUAL_MODE\n"
465 "- no-always-dual: ALWAYS_DUAL_MODE = DUAL_MODE\n"
466 "- dual: default behavior"),
469 {u
"B"_s, tr(
"Alias of --dual=noblock.")},
471 {u
"arg"_s, tr(
"Argument add to 'argv' variable in test scripts. Call this option several times to set multiple parameters."), tr(
"arg")},
474 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 "
475 "and 'this' refers to the internal object.\n"
476 "The {CODE} substring will be replaced by the test code."),
479 {u
"print-preamble"_s, tr(
"Show preamble.")},
482 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"
483 "Unfortunately, indentation files are not accessible with indentFiles(), nor are syntax files, according to the KSyntaxHighlighting compilation "
485 "This option cancels the value replacement.")},
487 {u
"X"_s, tr(
"force a value for XDG_DATA_DIRS and ignore -x."), tr(
"path")},
489 {{u
"S"_s, u
"set-variable"_s},
490 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 "
491 "option several times to set multiple parameters."),
497 {u
"no-color"_s, tr(
"No color on the output")},
499 {u
"color-reset"_s, tr(
"Sequence to reset color and style."), translatedColors},
500 {u
"color-success"_s, tr(
"Color for success."), translatedColors},
501 {u
"color-error"_s, tr(
"Color for error or exception."), translatedColors},
502 {u
"color-carret"_s, tr(
"Color for '^~~' under error position."), translatedColors},
503 {u
"color-debug-marker"_s, tr(
"Color for 'DEBUG:' and 'PRINT:' prefixes inserted with debug(), print() and printSep()."), translatedColors},
504 {u
"color-debug-message"_s, tr(
"Color for message with debug()."), translatedColors},
505 {u
"color-test-name"_s, tr(
"Color for name of the test."), translatedColors},
506 {u
"color-program"_s, tr(
"Color for program paramater in cmd() / test() and function name in stacktrace."), translatedColors},
507 {u
"color-file"_s, tr(
"Color for file name."), translatedColors},
508 {u
"color-line"_s, tr(
"Color for line number."), translatedColors},
509 {u
"color-block-selection-info"_s, tr(
"Color for [blockSelection=...] in a check."), translatedColors},
510 {u
"color-label-info"_s, tr(
"Color for 'input', 'output', 'result' label when it is displayed as information and not as an error."), translatedColors},
511 {u
"color-cursor"_s, tr(
"Color for cursor placeholder."), translatedColors},
512 {u
"color-selection"_s, tr(
"Color for selection placeholder."), translatedColors},
513 {u
"color-secondary-cursor"_s, tr(
"Color for secondary cursor placeholder."), translatedColors},
514 {u
"color-secondary-selection"_s, tr(
"Color for secondary selection placeholder."), translatedColors},
515 {u
"color-block-selection"_s, tr(
"Color for block selection placeholder."), translatedColors},
516 {u
"color-in-selection"_s, tr(
"Style added for text inside a selection."), translatedColors},
517 {u
"color-virtual-text"_s, tr(
"Color for virtual text placeholder."), translatedColors},
518 {u
"color-replacement"_s, tr(
"Color for text replaced by --format=placeholder."), translatedColors},
519 {u
"color-text-result"_s, tr(
"Color for text representing the inputs and outputs."), translatedColors},
521 tr(
"Color added to all colors used to display a result:\n"
523 "--color-selection\n"
524 "--color-secondary-cursor\n"
525 "--color-secondary-selection\n"
526 "--color-block-selection\n"
527 "--color-virtual-text\n"
528 "--color-replacement\n"
529 "--color-text-result."),
535struct CommandLineParseResult {
536 enum class Status {
Ok,
Error, VersionRequested, HelpRequested };
537 Status statusCode = Status::Ok;
541CommandLineParseResult parseCommandLine(
QCommandLineParser &parser, ScriptTesterQuery *query)
543 using Status = CommandLineParseResult::Status;
549 return {Status::Error, parser.
errorText()};
551 if (parser.
isSet(u
"v"_s))
552 return {Status::VersionRequested};
554 if (parser.
isSet(u
"h"_s))
555 return {Status::HelpRequested};
559 if (parser.
isSet(u
"q"_s)) {
560 query->executionConfig.maxError = 1;
562 if (parser.
isSet(u
"e"_s)) {
566 return {Status::Error, u
"--max-error: invalid number"_s};
569 query->executionConfig.xCheckAsFailure = parser.
isSet(u
"E"_s);
571 if (parser.
isSet(u
"s"_s)) {
577 const auto paths = parser.
values(u
"s"_s);
578 for (
const auto &path : paths) {
579 addPath(
query->paths.scripts, path + u
"/command"_sv);
580 addPath(
query->paths.scripts, path + u
"/indentation"_sv);
581 addPath(
query->paths.libraries, path + u
"/library"_sv);
582 addPath(
query->paths.files, path + u
"/files"_sv);
587 if (parser.
isSet(opt)) {
588 const auto paths = parser.
values(opt);
589 for (
const auto &path : paths) {
595 setPaths(
query->paths.scripts, u
"c"_s);
596 setPaths(
query->paths.libraries, u
"l"_s);
597 setPaths(
query->paths.files, u
"r"_s);
598 setPaths(
query->paths.modules, u
"m"_s);
599 query->paths.indentBaseDir = parser.
value(u
"I"_s);
601 if (parser.
isSet(u
"d"_s)) {
602 const auto value = parser.
value(u
"d"_s);
603 if (value == u
"location"_sv) {
604 query->format.debugOptions |= DebugOption::WriteLocation;
605 }
else if (value == u
"function"_sv) {
606 query->format.debugOptions |= DebugOption::WriteFunction;
607 }
else if (value == u
"stacktrace"_sv) {
608 query->format.debugOptions |= DebugOption::WriteStackTrace;
609 }
else if (value == u
"flush"_sv) {
610 query->format.debugOptions |= DebugOption::ForceFlush;
611 }
else if (value == u
"extended"_sv) {
612 query->extendedDebug =
true;
613 }
else if (value == u
"no-location"_sv) {
614 query->format.debugOptions.setFlag(DebugOption::WriteLocation,
false);
615 }
else if (value == u
"no-function"_sv) {
616 query->format.debugOptions.setFlag(DebugOption::WriteFunction,
false);
617 }
else if (value == u
"no-stacktrace"_sv) {
618 query->format.debugOptions.setFlag(DebugOption::WriteStackTrace,
false);
619 }
else if (value == u
"no-flush"_sv) {
620 query->format.debugOptions.setFlag(DebugOption::ForceFlush,
false);
621 }
else if (value == u
"no-extended"_sv) {
622 query->extendedDebug =
false;
623 }
else if (value == u
"all"_sv) {
624 query->extendedDebug =
true;
625 query->format.debugOptions = DebugOption::WriteLocation | DebugOption::WriteFunction | DebugOption::WriteStackTrace | DebugOption::ForceFlush;
626 }
else if (value == u
"none"_sv) {
627 query->extendedDebug =
false;
628 query->format.debugOptions = {};
630 return {Status::Error, u
"--debug: invalid value"_s};
634 if (parser.
isSet(u
"H"_s)) {
635 query->format.testFormatOptions |= TestFormatOption::HiddenTestName;
637 if (parser.
isSet(u
"p"_s)) {
638 query->format.testFormatOptions |= TestFormatOption::AlwaysWriteLocation;
640 if (parser.
isSet(u
"V"_s)) {
641 query->format.testFormatOptions |= TestFormatOption::AlwaysWriteInputOutput;
645 if (parser.
isSet(opt)) {
646 const auto value = parser.
value(opt);
647 if (value == u
"raw"_sv) {
648 textFormat = TextFormat::Raw;
649 }
else if (value == u
"js"_sv) {
650 textFormat = TextFormat::EscapeForDoubleQuote;
651 }
else if (value == u
"placeholder"_sv) {
652 textFormat = TextFormat::ReplaceNewLineAndTabWithPlaceholder;
653 }
else if (value == u
"placeholder2"_sv) {
654 textFormat = TextFormat::ReplaceTabWithPlaceholder;
655 }
else if (value == u
"literal"_sv) {
656 textFormat = TextFormat::ReplaceNewLineAndTabWithLiteral;
663 if (!setFormat(
query->format.documentTextFormat, u
"f"_s)) {
664 return {Status::Error, u
"--format: invalid value"_s};
666 if (!setFormat(
query->format.documentTextFormatWithBlockSelection, u
"F"_s)) {
667 return {Status::Error, u
"--block-format: invalid value"_s};
670 auto setPattern = [&parser, &
query](
QString opt, PatternType patternType) {
671 if (parser.
isSet(opt)) {
673 query->executionConfig.pattern.setPattern(parser.
value(opt));
674 if (!
query->executionConfig.pattern.isValid()) {
677 query->executionConfig.patternType = patternType;
682 if (!setPattern(u
"k"_s, PatternType::Include)) {
683 return {Status::Error, u
"-k: "_sv +
query->executionConfig.pattern.errorString()};
685 if (!setPattern(u
"K"_s, PatternType::Exclude)) {
686 return {Status::Error, u
"-K: "_sv +
query->executionConfig.pattern.errorString()};
689 if (parser.
isSet(u
"T"_s)) {
690 const auto tab = parser.
value(u
"T"_s);
691 if (tab.size() == 0) {
692 query->format.textReplacement.tab1 = defaultTextReplacement.tab1;
693 query->format.textReplacement.tab2 = defaultTextReplacement.tab2;
695 query->format.textReplacement.tab1 = tab[0];
696 query->format.textReplacement.tab2 = (tab.size() == 1) ?
query->format.textReplacement.tab1 : tab[1];
701 return str.
size() > i ? str[i] : c;
704 if (parser.
isSet(u
"N"_s)) {
705 const auto nl = parser.
value(u
"N"_s);
706 query->format.textReplacement.newLine = getChar(nl, 0,
query->format.textReplacement.newLine);
709 if (parser.
isSet(u
"P"_s)) {
710 const auto symbols = parser.
value(u
"P"_s);
711 auto &ph =
query->format.fallbackPlaceholders;
712 ph.cursor = getChar(symbols, 0, defaultFallbackPlaceholders.cursor);
713 ph.selectionStart = getChar(symbols, 1, defaultFallbackPlaceholders.selectionStart);
714 ph.selectionEnd = getChar(symbols, 2, defaultFallbackPlaceholders.selectionEnd);
715 ph.secondaryCursor = getChar(symbols, 3, defaultFallbackPlaceholders.secondaryCursor);
716 ph.secondarySelectionStart = getChar(symbols, 4, defaultFallbackPlaceholders.secondarySelectionStart);
717 ph.secondarySelectionEnd = getChar(symbols, 5, defaultFallbackPlaceholders.secondarySelectionEnd);
718 ph.virtualText = getChar(symbols, 6, defaultFallbackPlaceholders.virtualText);
721 if (parser.
isSet(u
"B"_s)) {
722 query->dualMode = DualMode::NoBlockSelection;
725 if (parser.
isSet(u
"b"_s)) {
726 const auto mode = parser.
value(u
"b"_s);
727 if (mode == u
"noblock"_sv) {
728 query->dualMode = DualMode::NoBlockSelection;
729 }
else if (mode == u
"block"_sv) {
730 query->dualMode = DualMode::BlockSelection;
731 }
else if (mode == u
"always-dual"_sv) {
732 query->dualMode = DualMode::DualIsAlwaysDual;
733 }
else if (mode == u
"no-always-dual"_sv) {
734 query->dualMode = DualMode::AlwaysDualIsDual;
735 }
else if (mode == u
"dual"_sv) {
736 query->dualMode = DualMode::Dual;
738 return {Status::Error, u
"--dual: invalid value"_s};
740 query->dualMode = DualMode::NoBlockSelection;
745 if (parser.
isSet(u
"preamble"_s)) {
746 query->preamble = parser.
value(u
"preamble"_s);
749 query->showPreamble = parser.
isSet(u
"print-preamble"_s);
751 if (parser.
isSet(u
"X"_s)) {
753 query->restoreXdgDataDirs =
true;
755 query->restoreXdgDataDirs = parser.
isSet(u
"x"_s);
758 if (parser.
isSet(u
"S"_s)) {
759 const auto variables = parser.
values(u
"S"_s);
762 for (
const auto &kv : variables) {
765 it->key = kv.sliced(0, pos);
766 it->value = kv.sliced(pos + 1);
774 query->diff.path = parser.
isSet(u
"D"_s) ? parser.
value(u
"D"_s) : u
"diff"_s;
776 const bool noColor = parser.
isSet(u
"no-color"_s);
778 if (parser.
isSet(u
"A"_s)) {
799 query->format.colors.blockSelectionInfo.
clear();
803 query->format.colors.secondarySelection.
clear();
808 query->format.colors.resultReplacement.
clear();
813 if (parser.
isSet(opt)) {
815 color = toANSIColor(parser.
value(opt), defaultResultColor, &ok);
824 setColor(
query->format.colors.reset, u
"color-reset"_s);
825 setColor(
query->format.colors.success, u
"color-success"_s);
826 setColor(
query->format.colors.error, u
"color-error"_s);
827 setColor(
query->format.colors.carret, u
"color-carret"_s);
828 setColor(
query->format.colors.debugMarker, u
"color-debug-marker"_s);
829 setColor(
query->format.colors.debugMsg, u
"color-debug-message"_s);
830 setColor(
query->format.colors.testName, u
"color-test-name"_s);
831 setColor(
query->format.colors.program, u
"color-program"_s);
832 setColor(
query->format.colors.fileName, u
"color-file"_s);
833 setColor(
query->format.colors.lineNumber, u
"color-line"_s);
834 setColor(
query->format.colors.labelInfo, u
"color-label-info"_s);
835 setColor(
query->format.colors.blockSelectionInfo, u
"color-block-selection-info"_s);
836 setColor(
query->format.colors.inSelection, u
"color-in-selection"_s);
838 if (!setColor(defaultResultColor, u
"color-result"_s)) {
839 defaultResultColor = u
"\x1b[40m"_s;
841 const bool hasDefault = defaultResultColor.
size();
843 if (!setColor(
query->format.colors.cursor, u
"color-cursor"_s) && hasDefault) {
844 query->format.colors.cursor = ansiBg % u
";1;33m"_sv;
846 if (!setColor(
query->format.colors.selection, u
"color-selection"_s) && hasDefault) {
847 query->format.colors.selection = ansiBg % u
";1;33m"_sv;
849 if (!setColor(
query->format.colors.secondaryCursor, u
"color-secondary-cursor"_s) && hasDefault) {
850 query->format.colors.secondaryCursor = ansiBg % u
";33m"_sv;
852 if (!setColor(
query->format.colors.secondarySelection, u
"color-secondary-selection"_s) && hasDefault) {
853 query->format.colors.secondarySelection = ansiBg % u
";33m"_sv;
855 if (!setColor(
query->format.colors.blockSelection, u
"color-block-selection"_s) && hasDefault) {
856 query->format.colors.blockSelection = ansiBg % u
";37m"_sv;
858 if (!setColor(
query->format.colors.virtualText, u
"color-virtual-text"_s) && hasDefault) {
859 query->format.colors.virtualText = ansiBg % u
";37m"_sv;
861 if (!setColor(
query->format.colors.result, u
"color-text-result"_s) && hasDefault) {
862 query->format.colors.result = defaultResultColor;
864 if (!setColor(
query->format.colors.resultReplacement, u
"color-replacement"_s) && hasDefault) {
865 query->format.colors.resultReplacement = ansiBg % u
";36m"_sv;
869 return {Status::Error, u
"--"_sv % optWithError % u
": invalid color"_sv};
878void addTextStyleProperties(
QJSValue &obj)
882 obj.
setProperty(u
"dsKeyword"_s, TextStyle::Keyword);
883 obj.
setProperty(u
"dsFunction"_s, TextStyle::Function);
884 obj.
setProperty(u
"dsVariable"_s, TextStyle::Variable);
885 obj.
setProperty(u
"dsControlFlow"_s, TextStyle::ControlFlow);
886 obj.
setProperty(u
"dsOperator"_s, TextStyle::Operator);
887 obj.
setProperty(u
"dsBuiltIn"_s, TextStyle::BuiltIn);
888 obj.
setProperty(u
"dsExtension"_s, TextStyle::Extension);
889 obj.
setProperty(u
"dsPreprocessor"_s, TextStyle::Preprocessor);
890 obj.
setProperty(u
"dsAttribute"_s, TextStyle::Attribute);
892 obj.
setProperty(u
"dsSpecialChar"_s, TextStyle::SpecialChar);
894 obj.
setProperty(u
"dsVerbatimString"_s, TextStyle::VerbatimString);
895 obj.
setProperty(u
"dsSpecialString"_s, TextStyle::SpecialString);
897 obj.
setProperty(u
"dsDataType"_s, TextStyle::DataType);
901 obj.
setProperty(u
"dsConstant"_s, TextStyle::Constant);
902 obj.
setProperty(u
"dsComment"_s, TextStyle::Comment);
903 obj.
setProperty(u
"dsDocumentation"_s, TextStyle::Documentation);
904 obj.
setProperty(u
"dsAnnotation"_s, TextStyle::Annotation);
905 obj.
setProperty(u
"dsCommentVar"_s, TextStyle::CommentVar);
906 obj.
setProperty(u
"dsRegionMarker"_s, TextStyle::RegionMarker);
907 obj.
setProperty(u
"dsInformation"_s, TextStyle::Information);
908 obj.
setProperty(u
"dsWarning"_s, TextStyle::Warning);
917static qsizetype timeNowInMs()
919 auto t = std::chrono::high_resolution_clock::now().time_since_epoch();
920 return std::chrono::duration_cast<std::chrono::milliseconds>(t).count();
923QtMessageHandler originalHandler =
nullptr;
929 if (originalHandler && context.category != std::string_view(
"kf.sonnet.core")) {
930 originalHandler(type, context, msg);
936int main(
int ac,
char **av)
938 ScriptTesterQuery
query;
939 query.xdgDataDirs = qgetenv(
"XDG_DATA_DIRS");
941 qputenv(
"QT_QPA_PLATFORM",
"offscreen");
946 qputenv(
"XDG_DATA_DIRS",
"/XDG_DATA_DIRS_unknown_folder");
948 originalHandler = qInstallMessageHandler(filterMessageOutput);
965 initCommandLineParser(app, parser);
967 using Status = CommandLineParseResult::Status;
968 CommandLineParseResult parseResult = parseCommandLine(parser, &query);
969 switch (parseResult.statusCode) {
972 std::fputs(
"No test file specified.\nUse -h / --help for more details.\n", stderr);
977 std::fputs(qPrintable(parseResult.errorString), stderr);
978 std::fputs(
"\nUse -h / --help for more details.\n", stderr);
980 case Status::VersionRequested:
983 case Status::HelpRequested:
984 std::fputs(qPrintable(parser.
helpText()), stdout);
987 Comma-separated list of values:
988 - color name: black, green, yellow, blue, magenta, cyan, white
989 - bright color name: bright-${color name}
990 - rgb: #fff or #ffffff (use trueColor sequence)
991 - background color: bg=${color name} or bg=bright-${color name} bg=${rgb}
992 - style: bold, dim, italic, underline, reverse, strike, doubly-underline, overlined
993 - ANSI sequence: number sequence with optional ';'
1004 auto jsInjectionStart1 =
1005 u
"(function(env, argv){"
1006 u
"const TestFramework = this.loadModule(':/ktexteditor/scripttester/testframework.js');"
1007 u
"const {REUSE_LAST_INPUT, REUSE_LAST_EXPECTED_OUTPUT} = TestFramework;"
1008 u
"const AS_INPUT = TestFramework.EXPECTED_OUTPUT_AS_INPUT;"
1009 u
"var {calleeWrapper, config, print, printSep, testCase, sequence, withInput, keys,"
1010 u
" indentFiles, test, xtest, eqvTrue, eqvFalse, eqTrue, eqFalse, error, errorMsg,"
1011 u
" errorType, hasError, eqv, is, eq, ne, lt, gt, le, ge, cmd, xcmd, type, xtype"
1012 u
" } = TestFramework;"
1013 u
"var c = TestFramework.sanitizeTag;"
1014 u
"var lazyfn = (fn, ...args) => new TestFramework.LazyFunc(fn, ...args);"
1016 u
"var lazyarg = (arg) => new TestFramework.LazyArg(arg);"
1017 u
"var arg = lazyarg;"
1018 u
"var loadScript = this.loadScript;"
1019 u
"var loadModule = this.loadModule;"
1020 u
"var paste = (str) => this.paste(str);"
1021 u
"env.editor = TestFramework.editor;"
1022 u
"var document = calleeWrapper('document', env.document);"
1023 u
"var editor = calleeWrapper('editor', env.editor);"
1024 u
"var view = calleeWrapper('view', env.view);"
1026 auto debugSetup =
query.extendedDebug ? u
"debug = testFramework.debug;"_sv : u
""_sv;
1028 auto dualModeSetup =
query.dualMode == DualMode::Dual
1029 ? u
"const DUAL_MODE = TestFramework.DUAL_MODE;"
1030 u
"const ALWAYS_DUAL_MODE = TestFramework.ALWAYS_DUAL_MODE;"_sv
1031 :
query.dualMode == DualMode::NoBlockSelection
1032 ? u
"const DUAL_MODE = 0;"
1033 u
"const ALWAYS_DUAL_MODE = 0;"_sv
1034 :
query.dualMode == DualMode::BlockSelection
1035 ? u
"const DUAL_MODE = 1;"
1036 u
"const ALWAYS_DUAL_MODE = 1;"_sv
1037 :
query.dualMode == DualMode::DualIsAlwaysDual
1038 ? u
"const DUAL_MODE = TestFramework.ALWAYS_DUAL_MODE;"
1039 u
"const ALWAYS_DUAL_MODE = TestFramework.ALWAYS_DUAL_MODE;"_sv
1041 : u
"const DUAL_MODE = TestFramework.DUAL_MODE;"
1042 u
"const ALWAYS_DUAL_MODE = TestFramework.DUAL_MODE;"_sv;
1044 auto jsInjectionStart2 =
1045 u
"var kbd = TestFramework.init(this, env, DUAL_MODE);"
1046 u
"try { void function(){"_sv;
1047 auto jsInjectionEnd =
1050 u
"if (e !== TestFramework.STOP_CASE_ERROR) {"
1058 const auto pattern = u
"{CODE}"_sv;
1060 auto pos = preamble.
indexOf(pattern);
1062 std::fputs(
"missing {CODE} with --preamble\n", stderr);
1065 jsInjectionStart1 = preamble.
sliced(0, pos);
1066 jsInjectionEnd = preamble.
sliced(pos + pattern.size());
1073 return jsInjectionStart1 % debugSetup % dualModeSetup % jsInjectionStart2 % u
'\n' % source % jsInjectionEnd;
1076 if (
query.showPreamble) {
1077 std::fputs(qPrintable(makeProgram(u
"{CODE}"_sv)), stdout);
1081 if (
query.restoreXdgDataDirs) {
1082 qputenv(
"XDG_DATA_DIRS",
query.xdgDataDirs);
1090 KTextEditor::ViewPrivate view(&doc,
nullptr);
1095 viewObj.setView(&view);
1098 docObj.setDocument(&doc);
1106 ScriptTester scriptTester(&output,
query.format,
query.paths,
query.executionConfig,
query.diff, defaultPlaceholder, &engine, &doc, &view);
1123 addTextStyleProperties(globalObject);
1127 scriptTester.require(u
"range.js"_s);
1131 "function i18n(text, ...arg) { return text; }\n"
1132 "function i18nc(context, text, ...arg) { return text; }\n"
1133 "function i18np(singular, plural, number, ...arg) { return number > 1 ? plural : singular; }\n"
1134 "function i18ncp(context, singular, plural, number, ...arg) { return number > 1 ? plural : singular; }\n"
1136 "var editor = undefined;"));
1143 for (quint32 i = 0; i <
query.argv.
size(); ++i) {
1147 const auto &colors =
query.format.colors;
1149 qsizetype delayInMs = 0;
1150 bool resetConfig =
false;
1151 auto runProgram = [&](
const QString &fileName,
const QString &source) {
1152 auto result = engine.
evaluate(makeProgram(source), fileName, 0);
1153 if (!result.isError()) {
1155 scriptTester.resetConfig();
1159 for (
const auto &variable : std::as_const(
query.variables)) {
1160 doc.setVariable(variable.key, variable.value);
1163 const auto start = timeNowInMs();
1164 result = result.callWithInstance(functions, {globalObject, jsArgv});
1165 delayInMs += timeNowInMs() -
start;
1166 if (!result.isError()) {
1171 scriptTester.incrementError();
1172 scriptTester.stream() << colors.error << result.toString() << colors.reset << u
'\n';
1173 scriptTester.writeException(result, u
"| "_sv);
1174 scriptTester.stream().flush();
1178 auto runJsFile = [&](
const QString &fileName) {
1184 scriptTester.incrementError();
1185 scriptTester.stream() << colors.fileName << fileName << colors.reset <<
": "_L1 << colors.error << file.
errorString() << colors.reset << u
'\n';
1186 scriptTester.stream().flush();
1191 runProgram(fileName, content);
1200 for (
const auto &fileName : fileNames) {
1202 runProgram(u
"file%1.js"_s.arg(&fileName -
fileNames.
data() + 1), fileName);
1203 }
else if (!
QFileInfo(fileName).isDir()) {
1204 runJsFile(fileName);
1207 while (it.hasNext() && !scriptTester.hasTooManyErrors()) {
1208 runJsFile(it.next());
1212 if (scriptTester.hasTooManyErrors()) {
1221 if (scriptTester.hasTooManyErrors()) {
1222 scriptTester.stream() << colors.error <<
"Too many error"_L1 << colors.reset << u
'\n';
1225 scriptTester.writeSummary();
1226 scriptTester.stream() <<
" Duration: "_L1 << delayInMs <<
"ms\n"_L1;
1227 scriptTester.stream().flush();
1229 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 Q_NOREPLY void start()
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 const
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