diff --git a/README.md b/README.md index c44ade3..ba3bf41 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,16 @@ A dead-simple Markdown writing app built with Qt Quick and C++ that automaticall Install via the Omarchy Package Repository via the `omawrite` package. It's installed by default in new installations of Omarchy (from Quattro forward). +## Command line + +`omawrite` opens an empty document, and `omawrite FILE` opens a Markdown file. +`omawrite --help` prints that usage without opening a window. + +That last one is mostly for coding agents. Ask one to open a file in Omawrite +and it runs `omawrite --help` first to work out how, so the help has to answer +in the terminal, and it has to say that the process stays up until the window +is closed. + ## Shortcuts - `Ctrl+S` saves. Unsaved documents use the XDG desktop portal file picker. diff --git a/omawrite.pro b/omawrite.pro index 422f1d8..f14efc8 100644 --- a/omawrite.pro +++ b/omawrite.pro @@ -6,12 +6,14 @@ TEMPLATE = app HEADERS += \ src/backend.h \ + src/cli.h \ src/markdownhighlighter.h \ src/systemtheme.h SOURCES += \ src/main.cpp \ src/backend.cpp \ + src/cli.cpp \ src/markdownhighlighter.cpp \ src/systemtheme.cpp diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 0000000..ce7c34a --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,56 @@ +#include "cli.h" + +#include + +QString Cli::usage() { + return QStringLiteral( + "Omawrite is a dead-simple Markdown writing app.\n" + "\n" + "Usage:\n" + " omawrite [FILE]\n" + "\n" + "Opens the Omawrite window. FILE is the Markdown file to open; without\n" + "one, Omawrite starts on an empty document, or on the draft recovered\n" + "from the last session.\n" + "\n" + "Options:\n" + " -h, --help Show this message and exit\n" + "\n" + "Omawrite is a graphical app: it needs a desktop session, and it keeps\n" + "running until the window is closed. Scripts and agents that only want\n" + "a file on screen should background it:\n" + "\n" + " omawrite draft.md &\n" + "\n" + "Everything else is a keyboard shortcut. Ctrl+? lists them in the app,\n" + "and https://github.com/omacom-io/omawrite#shortcuts has the same list.\n"); +} + +std::optional Cli::handleArguments(const QStringList &arguments) { + for (const QString &argument : arguments.mid(1)) { + if (argument == QLatin1String("-h") || argument == QLatin1String("--help")) { + QTextStream(stdout) << usage(); + return 0; + } + + // A leading dash means an option was meant, not a file. Answering with + // the usage beats opening a window for a file that cannot exist. + if (argument.startsWith(QLatin1Char('-')) && argument != QLatin1String("-")) { + QTextStream(stderr) << QStringLiteral("omawrite: unrecognized option '%1'\n\n") + .arg(argument) + << usage(); + return 1; + } + } + + return std::nullopt; +} + +std::optional Cli::handleArguments(int argc, char *argv[]) { + QStringList arguments; + arguments.reserve(argc); + for (int index = 0; index < argc; ++index) + arguments.append(QString::fromLocal8Bit(argv[index])); + + return handleArguments(arguments); +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..86e3729 --- /dev/null +++ b/src/cli.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +// Command line handling that runs before the GUI starts, so `omawrite --help` +// answers in the terminal instead of opening a window. +namespace Cli { + +QString usage(); + +// The exit code main should return when the arguments are answered without +// starting the app, or nothing when Omawrite should open as usual. +std::optional handleArguments(const QStringList &arguments); +std::optional handleArguments(int argc, char *argv[]); + +} diff --git a/src/main.cpp b/src/main.cpp index 8b22213..d679735 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,15 @@ #include #include "backend.h" +#include "cli.h" #include "systemtheme.h" int main(int argc, char *argv[]) { + // Answer --help before Qt claims the terminal, so the usage prints even + // without a desktop session to open a window in. + if (const std::optional exitCode = Cli::handleArguments(argc, argv)) + return *exitCode; + QApplication app(argc, argv); app.setApplicationName(QStringLiteral("omawrite")); app.setDesktopFileName(QStringLiteral("omawrite")); diff --git a/tests/tests.pro b/tests/tests.pro index 5e564a2..5e8cc7d 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -7,9 +7,11 @@ INCLUDEPATH += ../src SOURCES += \ tst_omawrite.cpp \ ../src/backend.cpp \ + ../src/cli.cpp \ ../src/markdownhighlighter.cpp HEADERS += \ ../src/backend.h \ + ../src/cli.h \ ../src/markdownhighlighter.h QT += widgets printsupport quickcontrols2 quickdialogs2 dbus diff --git a/tests/tst_omawrite.cpp b/tests/tst_omawrite.cpp index 5c3306a..43038b1 100644 --- a/tests/tst_omawrite.cpp +++ b/tests/tst_omawrite.cpp @@ -6,6 +6,7 @@ #include #include "backend.h" +#include "cli.h" #include "markdownhighlighter.h" class OmawriteTest : public QObject { @@ -44,6 +45,22 @@ private slots: QStringLiteral("Already.md")); } + void answersHelpBeforeOpeningAWindow() { + const QString usage = Cli::usage(); + QVERIFY(usage.contains(QStringLiteral("omawrite [FILE]"))); + QVERIFY(usage.contains(QStringLiteral("-h, --help"))); + + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("--help")}), + std::optional(0)); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("-h")}), + std::optional(0)); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("--nope")}), + std::optional(1)); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite")}), std::nullopt); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("draft.md")}), + std::nullopt); + } + void findsInlineMarkdownRanges() { const auto markup = MarkdownHighlighter::inlineMarkup( QStringLiteral("**bold** and *italic* and [site](https://example.com)"));