diff --git a/BUILD.md b/BUILD.md index 4a1f8f14..c61342fc 100644 --- a/BUILD.md +++ b/BUILD.md @@ -198,11 +198,33 @@ To build with VS 2017 or later in Command Prompt: "%JOM%" install ``` -# Linux +# Linux and Other freedesktop.org-conforming (XDG) Desktop Systems - - Install gcc and qt5 - - Optionally install fcitx5-qt for building with static version of Qt - - Open `Red_Panda_CPP.pro` with Qt Creator +General steps: + +- Install recent version of GCC (≥ 7) or Clang (≥ 6) that supports C++17. +- Install Qt 5 (≥ 5.12) Base, SVG and Tools modules, including both libraries and development files. +- Optionally install fcitx5-qt for building with static Qt library. +- Optionally install Qt Creator for development. + +For build only: + +1. Configure: + ```bash + qmake PREFIX=/usr/local /path/to/src/Red_Panda_CPP.pro + ``` +2. Make: + ```bash + make -j$(nproc) + ``` +3. Install: + ```bash + sudo make install + ``` + +For development: + +1. Open `Red_Panda_CPP.pro` with Qt Creator qmake variables: - `PREFIX`: default to `/usr/local`. It should be set to `/usr` or `/opt/redpanda-cpp` when packaging. @@ -296,7 +318,7 @@ Enter `RedPandaIDE` to launch RedPanda C++. Note that makepkg checks out HEAD of the repo, so any change should be committed before building. -## AppImage +## Linux AppImage 1. Install dependency: Docker or Podman. @@ -336,9 +358,9 @@ Note that makepkg checks out HEAD of the repo, so any change should be committed It is possible to build Red Panda C++ for foreign architectures using targets’ native toolchains with QEMU user space emulation. -Note: Always run emulated native build **in containers**. Mixing architectures may kill your system. +Note: Always run emulated native build **in containers or jails**. Mixing architectures may kill your system. -For Linux host, install statically linked QEMU user space emulator (package name is likely `qemu-user-static`) and make sure that binfmt support is enabled. +For Linux or BSD host, install statically linked QEMU user space emulator (package name is likely `qemu-user-static`) and make sure that binfmt support is enabled. For Windows host, Docker and Podman should have QEMU user space emulation enabled. If not, * For Docker: @@ -350,3 +372,38 @@ For Windows host, Docker and Podman should have QEMU user space emulation enable wsl -d podman-machine-default sudo cp /usr/lib/binfmt.d/qemu-aarch64-static.conf /proc/sys/fs/binfmt_misc/register wsl -d podman-machine-default sudo cp /usr/lib/binfmt.d/qemu-riscv64-static.conf /proc/sys/fs/binfmt_misc/register ``` + +# macOS + +## Qt.io Qt Library + +Prerequisites: + +0. macOS 10.13 or later. +1. Install Xcode Command Line Tools: + ```zsh + xcode-select --install + ``` +2. Install Qt with online installer from [Qt.io](https://www.qt.io/download-qt-installer-oss). + - Select the library (in _Qt_ group, _Qt 5.15.2_ subgroup, check _macOS_). + +Build: + +1. Set related variables: + ```bash + SRC_DIR="~/redpanda-src" + BUILD_DIR="~/redpanda-build" + INSTALL_DIR="~/redpanda-pkg" + ``` +2. Navigate to build directory: + ```bash + rm -rf "$BUILD_DIR" # optional for clean build + mkdir -p "$BUILD_DIR" && cd "$BUILD_DIR" + ``` +3. Configure, build and install: + ```bash + ~/Qt/5.15.2/clang_64/bin/qmake PREFIX="$INSTALL_DIR" "$SRC_DIR/Red_Panda_CPP.pro" + make -j$(sysctl -n hw.logicalcpu) + make install + ~/Qt/5.15.2/clang_64/bin/macdeployqt "$INSTALL_DIR/bin/RedPandaIDE.app" + ``` diff --git a/BUILD_cn.md b/BUILD_cn.md index e92228c6..41159fef 100644 --- a/BUILD_cn.md +++ b/BUILD_cn.md @@ -198,12 +198,33 @@ "%JOM%" install ``` -# Linux +# Linux 和其他符合 freedesktop.org(XDG)规范的桌面系统 -步骤: - - 安装 gcc 和 qt5开发相关包 - - 如果使用静态版本的 Qt 编译,还要安装 fcitx5-qt - - 使用qtcreator打开Red_Panda_CPP.pro文件 +通用步骤: + +- 安装支持 C++17 的 GCC(≥ 7)或 Clang(≥ 6)。 +- 安装 Qt 5(≥ 5.12)Base、SVG、Tools 模块,包括库和开发文件。 +- 如果使用静态版本的 Qt 编译,还要安装 fcitx5-qt。 +- 根据需要,安装 Qt Creator 用于开发。 + +仅构建: + +1. 配置: + ```bash + qmake PREFIX=/usr/local /path/to/src/Red_Panda_CPP.pro + ``` +2. 构建: + ```bash + make -j$(nproc) + ``` +3. 安装: + ```bash + sudo make install + ``` + +开发: + +1. 使用 Qt Creator 打开 `Red_Panda_CPP.pro` 文件。 qmake 变量: - `PREFIX`:默认值是 `/usr/local`。打包时应该定义为 `/usr` 或 `/opt/redpanda-cpp`。 @@ -297,7 +318,7 @@ Windows 宿主的额外要求: 注意:makepkg 签出此存储库的 HEAD,因此构建之前务必提交所有变更。 -## AppImage +## Linux AppImage 1. 安装依赖包:Docker 或 Podman。 @@ -337,9 +358,9 @@ Windows 宿主的额外要求: 可以借助 QEMU 用户空间模拟,运行目标架构的本机工具链,来构建小熊猫 C++。 -注意:始终**在容器中**运行模拟本机构建,因为混用不同架构的程序和库可能会损坏系统。 +注意:始终**在容器或 jail 中**运行模拟本机构建,因为混用不同架构的程序和库可能会损坏系统。 -对于 Linux 宿主,需要安装静态链接的 QEMU 用户空间模拟器(包名通常为 `qemu-user-static`)并确认已经启用 binfmt 支持。 +对于 Linux 或 BSD 宿主,需要安装静态链接的 QEMU 用户空间模拟器(包名通常为 `qemu-user-static`)并确认已经启用 binfmt 支持。 对于 Windows 宿主,Docker 和 Podman 应该已经启用了 QEMU 用户空间模拟。如果没有启用, * Docker: @@ -351,3 +372,38 @@ Windows 宿主的额外要求: wsl -d podman-machine-default sudo cp /usr/lib/binfmt.d/qemu-aarch64-static.conf /proc/sys/fs/binfmt_misc/register wsl -d podman-machine-default sudo cp /usr/lib/binfmt.d/qemu-riscv64-static.conf /proc/sys/fs/binfmt_misc/register ``` + +# macOS + +## Qt.io 的 Qt 库 + +前置条件: + +0. macOS 10.13 或更高版本。 +1. 安装 Xcode 命令行工具: + ```bash + xcode-select --install + ``` +2. 用 [Qt.io](https://www.qt.io/download-qt-installer-oss) 或[镜像站](https://mirrors.sjtug.sjtu.edu.cn/docs/qt)的在线安装器安装 Qt。 + - 选中 Qt 库(“Qt” 组下的 “Qt 5.15.2” 小组,勾选 “macOS”)。 + +构建: + +1. 设置相关变量: + ```bash + SRC_DIR="~/redpanda-src" + BUILD_DIR="~/redpanda-build" + INSTALL_DIR="~/redpanda-pkg" + ``` +2. 定位到构建目录: + ```bash + rm -rf "$BUILD_DIR" # 根据需要进行全新构建 + mkdir -p "$BUILD_DIR" && cd "$BUILD_DIR" + ``` +3. 配置、构建、安装: + ```bash + ~/Qt/5.15.2/clang_64/bin/qmake PREFIX="$INSTALL_DIR" "$SRC_DIR/Red_Panda_CPP.pro" + make -j$(sysctl -n hw.logicalcpu) + make install + ~/Qt/5.15.2/clang_64/bin/macdeployqt "$INSTALL_DIR/bin/RedPandaIDE.app" + ``` diff --git a/RedPandaIDE/RedPandaIDE.pro b/RedPandaIDE/RedPandaIDE.pro index e9093059..7cb815a3 100644 --- a/RedPandaIDE/RedPandaIDE.pro +++ b/RedPandaIDE/RedPandaIDE.pro @@ -29,10 +29,6 @@ contains(QMAKE_HOST.arch, x86_64):{ } macos: { - # This package needs to be installed via homebrew before we can compile it - INCLUDEPATH += \ - /opt/homebrew/opt/icu4c/include - QT += gui-private ICON = ../macos/RedPandaIDE.icns @@ -131,6 +127,7 @@ SOURCES += \ settingsdialog/editortooltipswidget.cpp \ settingsdialog/environmentfolderswidget.cpp \ settingsdialog/environmentperformancewidget.cpp \ + settingsdialog/environmentprogramswidget.cpp \ settingsdialog/environmentshortcutwidget.cpp \ settingsdialog/executorproblemsetwidget.cpp \ settingsdialog/formattergeneralwidget.cpp \ @@ -254,6 +251,7 @@ HEADERS += \ settingsdialog/editortooltipswidget.h \ settingsdialog/environmentfolderswidget.h \ settingsdialog/environmentperformancewidget.h \ + settingsdialog/environmentprogramswidget.h \ settingsdialog/environmentshortcutwidget.h \ settingsdialog/executorproblemsetwidget.h \ settingsdialog/formattergeneralwidget.h \ @@ -349,6 +347,7 @@ FORMS += \ settingsdialog/editortooltipswidget.ui \ settingsdialog/environmentfolderswidget.ui \ settingsdialog/environmentperformancewidget.ui \ + settingsdialog/environmentprogramswidget.ui \ settingsdialog/environmentshortcutwidget.ui \ settingsdialog/executorproblemsetwidget.ui \ settingsdialog/formattergeneralwidget.ui \ @@ -469,21 +468,18 @@ win32: { unix: { HEADERS += \ - settingsdialog/formatterpathwidget.h \ - settingsdialog/environmentprogramswidget.h + settingsdialog/formatterpathwidget.h SOURCES += \ - settingsdialog/formatterpathwidget.cpp \ - settingsdialog/environmentprogramswidget.cpp + settingsdialog/formatterpathwidget.cpp FORMS += \ - settingsdialog/formatterpathwidget.ui \ - settingsdialog/environmentprogramswidget.ui + settingsdialog/formatterpathwidget.ui } linux: { - LIBS+= \ - -lrt + # legacy glibc compatibility -- modern Unices have all components in `libc.so` + LIBS += -lrt _LINUX_STATIC_IME_PLUGIN = $$(LINUX_STATIC_IME_PLUGIN) equals(_LINUX_STATIC_IME_PLUGIN, "ON") { diff --git a/RedPandaIDE/autolinkmanager.cpp b/RedPandaIDE/autolinkmanager.cpp index 1fc531d7..f2d9864d 100644 --- a/RedPandaIDE/autolinkmanager.cpp +++ b/RedPandaIDE/autolinkmanager.cpp @@ -62,11 +62,13 @@ void AutolinkManager::load() file.write(content); file.close(); preFile.close(); -#elif defined(Q_OS_LINUX) - QFile preFile(":/config/autolink-linux.json"); +#elif defined(Q_OS_MACOS) + return; +#else // XDG desktop + QFile preFile(":/config/autolink-xdg.json"); if (!preFile.open(QFile::ReadOnly)) { throw FileError(QObject::tr("Can't open file '%1' for read.") - .arg(":/config/autolink-linux.json")); + .arg(":/config/autolink-xdg.json")); } QByteArray content=preFile.readAll(); if (!file.open(QFile::WriteOnly|QFile::Truncate)) { @@ -76,8 +78,6 @@ void AutolinkManager::load() file.write(content); file.close(); preFile.close(); -#else - return; #endif } if (file.open(QFile::ReadOnly)) { diff --git a/RedPandaIDE/compiler/compilerinfo.cpp b/RedPandaIDE/compiler/compilerinfo.cpp index 907beddc..4e21b1d8 100644 --- a/RedPandaIDE/compiler/compilerinfo.cpp +++ b/RedPandaIDE/compiler/compilerinfo.cpp @@ -177,9 +177,12 @@ void CompilerInfo::prepareCompilerOptions() sl.append(QPair("Strong","-strong")); sl.append(QPair("All","-all")); addOption(CC_CMD_OPT_STACK_PROTECTOR , QObject::tr("Check for stack smashing attacks (-fstack-protector)"), groupName, false, false, true, "-fstack-protector", CompilerOptionType::Choice, sl); -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) +#if defined(Q_OS_UNIX) sl.clear(); sl.append(QPair("Address","address")); +# ifdef __aarch64__ + sl.append(QPair("Hardware-assisted Address","hwaddress")); +# endif sl.append(QPair("Thread","thread")); sl.append(QPair("Leak","leak")); sl.append(QPair("Undefined","undefined")); diff --git a/RedPandaIDE/compiler/compilermanager.cpp b/RedPandaIDE/compiler/compilermanager.cpp index 255474fc..44ddbe7e 100644 --- a/RedPandaIDE/compiler/compilermanager.cpp +++ b/RedPandaIDE/compiler/compilermanager.cpp @@ -31,6 +31,9 @@ #include #include #include "projectcompiler.h" +#ifdef Q_OS_MACOS +#include +#endif enum RunProgramFlag { RPF_PAUSE_CONSOLE = 0x0001, @@ -255,20 +258,32 @@ void CompilerManager::run( #ifdef Q_OS_WIN if (consoleFlag!=0) { QString sharedMemoryId = QUuid::createUuid().toString(); - QString newArguments = QString(" %1 %2 \"%3\" %4") - .arg(consoleFlag) - .arg(sharedMemoryId,localizePath(filename)).arg(arguments); - + QString consolePauserPath = includeTrailingPathDelimiter(pSettings->dirs().appDir()) + CONSOLE_PAUSER; + QStringList execArgs = QStringList{ + consolePauserPath, + QString::number(consoleFlag), + sharedMemoryId, + localizePath(filename) + } + splitProcessCommand(arguments); + auto [filename, args, fileOwner] = wrapCommandForTerminalEmulator( + pSettings->environment().terminalPathForExec(), + pSettings->environment().terminalArgumentsPattern(), + execArgs + ); //delete when thread finished - execRunner = new ExecutableRunner(includeTrailingPathDelimiter(pSettings->dirs().appDir())+CONSOLE_PAUSER,newArguments,workDir); + execRunner = new ExecutableRunner(filename, args, workDir); execRunner->setShareMemoryId(sharedMemoryId); + mTempFileOwner = std::move(fileOwner); } else { //delete when thread finished - execRunner = new ExecutableRunner(filename,arguments,workDir); + execRunner = new ExecutableRunner(filename,splitProcessCommand(arguments),workDir); } #else - QString newArguments; + QStringList execArgs; QString sharedMemoryId = "/r"+QUuid::createUuid().toString(QUuid::StringFormat::Id128); +#ifdef Q_OS_MACOS + sharedMemoryId = sharedMemoryId.mid(0, PSHMNAMLEN); +#endif if (consoleFlag!=0) { QString consolePauserPath=includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"consolepauser"; if (!fileExists(consolePauserPath)) { @@ -280,31 +295,40 @@ void CompilerManager::run( } if (redirectInput) { - newArguments = QString(" -e \"%1\" %2 %3 \"%4\" \"%5\" %6") - .arg(consolePauserPath) - .arg(consoleFlag) - .arg(sharedMemoryId) - .arg(escapeSpacesInString(redirectInputFilename)) - .arg(localizePath(escapeSpacesInString(filename))) - .arg(arguments); + execArgs = QStringList{ + consolePauserPath, + QString::number(consoleFlag), + sharedMemoryId, + redirectInputFilename, + localizePath(filename), + } + splitProcessCommand(arguments); } else { - newArguments = QString(" -e \"%1\" %2 %3 \"%4\" %5") - .arg(consolePauserPath) - .arg(consoleFlag) - .arg(sharedMemoryId,localizePath(escapeSpacesInString(filename))).arg(arguments); + execArgs = QStringList{ + consolePauserPath, + QString::number(consoleFlag), + sharedMemoryId, + localizePath(filename), + } + splitProcessCommand(arguments); } } else { - newArguments = QString(" -e \"%1\" %2") - .arg(localizePath(escapeSpacesInString(filename))).arg(arguments); + execArgs = QStringList{ + localizePath(filename), + } + splitProcessCommand(arguments); } - execRunner = new ExecutableRunner(pSettings->environment().terminalPathForExec(),newArguments,workDir); + auto [filename, args, fileOwner] = wrapCommandForTerminalEmulator( + pSettings->environment().terminalPathForExec(), + pSettings->environment().terminalArgumentsPattern(), + execArgs + ); + execRunner = new ExecutableRunner(filename, args, workDir); execRunner->setShareMemoryId(sharedMemoryId); + mTempFileOwner = std::move(fileOwner); #endif execRunner->setStartConsole(true); } else { //delete when thread finished - execRunner = new ExecutableRunner(filename,arguments,workDir); - } + execRunner = new ExecutableRunner(filename,splitProcessCommand(arguments),workDir); + } if (redirectInput) { execRunner->setRedirectInput(true); execRunner->setRedirectInputFilename(redirectInputFilename); @@ -347,7 +371,7 @@ void CompilerManager::doRunProblem(const QString &filename, const QString &argum if (mRunner!=nullptr) { return; } - OJProblemCasesRunner * execRunner = new OJProblemCasesRunner(filename,arguments,workDir,problemCases); + OJProblemCasesRunner * execRunner = new OJProblemCasesRunner(filename,splitProcessCommand(arguments),workDir,problemCases); mRunner = execRunner; if (pSettings->executor().enableCaseLimit()) { execRunner->setExecTimeout(pSettings->executor().caseTimeout()); @@ -380,6 +404,7 @@ void CompilerManager::stopRun() mRunner->stop(); disconnect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated); mRunner=nullptr; + mTempFileOwner=nullptr; } } @@ -395,6 +420,7 @@ void CompilerManager::stopPausing() disconnect(mRunner, &Runner::finished, this ,&CompilerManager::onRunnerTerminated); mRunner->stop(); mRunner=nullptr; + mTempFileOwner=nullptr; } } @@ -428,6 +454,7 @@ void CompilerManager::onRunnerTerminated() { QMutexLocker locker(&mRunnerMutex); mRunner=nullptr; + mTempFileOwner=nullptr; } void CompilerManager::onRunnerPausing() @@ -438,6 +465,7 @@ void CompilerManager::onRunnerPausing() disconnect(mRunner, &Runner::runErrorOccurred, pMainWindow ,&MainWindow::onRunErrorOccured); connect(this, &CompilerManager::signalStopAllRunners, mRunner, &Runner::stop); mRunner=nullptr; + mTempFileOwner=nullptr; } void CompilerManager::onCompileIssue(PCompileIssue issue) diff --git a/RedPandaIDE/compiler/compilermanager.h b/RedPandaIDE/compiler/compilermanager.h index 7bfb209c..76c3df25 100644 --- a/RedPandaIDE/compiler/compilermanager.h +++ b/RedPandaIDE/compiler/compilermanager.h @@ -20,6 +20,7 @@ #include #include #include "qt_utils/utils.h" +#include "../utils.h" #include "../common.h" class Runner; @@ -96,6 +97,7 @@ private: int mSyntaxCheckIssueCount; Compiler* mBackgroundSyntaxChecker; Runner* mRunner; + TemporaryFileOwner mTempFileOwner; #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QRecursiveMutex mCompileMutex; QRecursiveMutex mBackgroundSyntaxCheckMutex; diff --git a/RedPandaIDE/compiler/executablerunner.cpp b/RedPandaIDE/compiler/executablerunner.cpp index db802ed4..f4297f46 100644 --- a/RedPandaIDE/compiler/executablerunner.cpp +++ b/RedPandaIDE/compiler/executablerunner.cpp @@ -23,7 +23,7 @@ #ifdef Q_OS_WIN #include #include -#elif defined(Q_OS_LINUX) +#else #include #include #include @@ -32,7 +32,7 @@ #endif -ExecutableRunner::ExecutableRunner(const QString &filename, const QString &arguments, const QString &workDir +ExecutableRunner::ExecutableRunner(const QString &filename, const QStringList &arguments, const QString &workDir ,QObject* parent): Runner(filename,arguments,workDir,parent), mRedirectInput(false), @@ -110,7 +110,7 @@ void ExecutableRunner::run() mProcess = std::make_shared(); mProcess->setProgram(mFilename); - mProcess->setArguments(splitProcessCommand(mArguments)); + mProcess->setArguments(mArguments); //qDebug()<setWorkingDirectory(mWorkDir); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); @@ -162,7 +162,7 @@ void ExecutableRunner::run() } } } -#elif defined(Q_OS_LINUX) +#else int BUF_SIZE=1024; char* pBuf=nullptr; int fd_shm = shm_open(mShareMemoryId.toLocal8Bit().data(),O_RDWR | O_CREAT,S_IRWXU); @@ -214,7 +214,6 @@ void ExecutableRunner::run() } break; } -#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) if (mStartConsole && !mPausing && pBuf) { if (strncmp(pBuf,"FINISHED",sizeof("FINISHED"))==0) { #ifdef Q_OS_WIN @@ -226,7 +225,7 @@ void ExecutableRunner::run() hSharedMemory = INVALID_HANDLE_VALUE; CloseHandle(hSharedMemory); } -#elif defined(Q_OS_LINUX) +#else if (pBuf) { munmap(pBuf,BUF_SIZE); pBuf = nullptr; @@ -240,14 +239,13 @@ void ExecutableRunner::run() emit pausingForFinish(); } } -#endif } #ifdef Q_OS_WIN if (pBuf) UnmapViewOfFile(pBuf); if (hSharedMemory!=INVALID_HANDLE_VALUE && hSharedMemory!=NULL) CloseHandle(hSharedMemory); -#elif defined(Q_OS_LINUX) +#else if (pBuf) { munmap(pBuf,BUF_SIZE); } diff --git a/RedPandaIDE/compiler/executablerunner.h b/RedPandaIDE/compiler/executablerunner.h index 8ccb9962..c20ce582 100644 --- a/RedPandaIDE/compiler/executablerunner.h +++ b/RedPandaIDE/compiler/executablerunner.h @@ -25,7 +25,7 @@ class ExecutableRunner : public Runner { Q_OBJECT public: - ExecutableRunner(const QString& filename, const QString& arguments, const QString& workDir, + ExecutableRunner(const QString& filename, const QStringList& arguments, const QString& workDir, QObject* parent = nullptr); ExecutableRunner(const ExecutableRunner&)=delete; ExecutableRunner& operator=(const ExecutableRunner&)=delete; diff --git a/RedPandaIDE/compiler/ojproblemcasesrunner.cpp b/RedPandaIDE/compiler/ojproblemcasesrunner.cpp index 40a72e08..67b11a7c 100644 --- a/RedPandaIDE/compiler/ojproblemcasesrunner.cpp +++ b/RedPandaIDE/compiler/ojproblemcasesrunner.cpp @@ -25,7 +25,7 @@ #endif -OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QString& arguments, const QString& workDir, +OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QStringList& arguments, const QString& workDir, const QVector& problemCases, QObject *parent): Runner(filename,arguments,workDir,parent), mExecTimeout(0), @@ -37,7 +37,7 @@ OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QStrin setWaitForFinishTime(100); } -OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QString& arguments, const QString& workDir, +OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QStringList& arguments, const QString& workDir, POJProblemCase problemCase, QObject *parent): Runner(filename,arguments,workDir,parent), mExecTimeout(0), @@ -64,7 +64,7 @@ void OJProblemCasesRunner::runCase(int index,POJProblemCase problemCase) QElapsedTimer elapsedTimer; bool execTimeouted = false; process.setProgram(mFilename); - process.setArguments(splitProcessCommand(mArguments)); + process.setArguments(mArguments); process.setWorkingDirectory(mWorkDir); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH"); diff --git a/RedPandaIDE/compiler/ojproblemcasesrunner.h b/RedPandaIDE/compiler/ojproblemcasesrunner.h index 6162899d..bc443388 100644 --- a/RedPandaIDE/compiler/ojproblemcasesrunner.h +++ b/RedPandaIDE/compiler/ojproblemcasesrunner.h @@ -25,10 +25,10 @@ class OJProblemCasesRunner : public Runner { Q_OBJECT public: - explicit OJProblemCasesRunner(const QString& filename, const QString& arguments, const QString& workDir, + explicit OJProblemCasesRunner(const QString& filename, const QStringList& arguments, const QString& workDir, const QVector& problemCases, QObject *parent = nullptr); - explicit OJProblemCasesRunner(const QString& filename, const QString& arguments, const QString& workDir, + explicit OJProblemCasesRunner(const QString& filename, const QStringList& arguments, const QString& workDir, POJProblemCase problemCase, QObject *parent = nullptr); OJProblemCasesRunner(const OJProblemCasesRunner&)=delete; diff --git a/RedPandaIDE/compiler/runner.cpp b/RedPandaIDE/compiler/runner.cpp index bb1f8c42..c341d704 100644 --- a/RedPandaIDE/compiler/runner.cpp +++ b/RedPandaIDE/compiler/runner.cpp @@ -17,7 +17,7 @@ #include "runner.h" #include -Runner::Runner(const QString &filename, const QString &arguments, const QString &workDir +Runner::Runner(const QString &filename, const QStringList &arguments, const QString &workDir ,QObject *parent) : QThread(parent), mPausing(false), mStop(false), diff --git a/RedPandaIDE/compiler/runner.h b/RedPandaIDE/compiler/runner.h index 5de99eda..08b297b8 100644 --- a/RedPandaIDE/compiler/runner.h +++ b/RedPandaIDE/compiler/runner.h @@ -23,7 +23,7 @@ class Runner : public QThread { Q_OBJECT public: - explicit Runner(const QString& filename, const QString& arguments, const QString& workDir, QObject *parent = nullptr); + explicit Runner(const QString& filename, const QStringList& arguments, const QString& workDir, QObject *parent = nullptr); Runner(const Runner&)=delete; Runner operator=(const Runner&)=delete; @@ -48,7 +48,7 @@ protected: bool mPausing; bool mStop; QString mFilename; - QString mArguments; + QStringList mArguments; // without argv[0] QString mWorkDir; int mWaitForFinishTime; }; diff --git a/RedPandaIDE/defaultconfigs.qrc b/RedPandaIDE/defaultconfigs.qrc index 9043f715..bdd1afea 100644 --- a/RedPandaIDE/defaultconfigs.qrc +++ b/RedPandaIDE/defaultconfigs.qrc @@ -2,6 +2,6 @@ resources/autolink.json resources/codesnippets.json - resources/autolink-linux.json + resources/autolink-xdg.json diff --git a/RedPandaIDE/main.cpp b/RedPandaIDE/main.cpp index 09436174..98092b40 100644 --- a/RedPandaIDE/main.cpp +++ b/RedPandaIDE/main.cpp @@ -248,6 +248,23 @@ int main(int argc, char *argv[]) qputenv("QT_QPA_PLATFORM", "windows:darkmode=2"); #endif +#ifdef Q_OS_MACOS + // in macOS GUI apps, `/usr/local/bin` is not in PATH by default + // follow the Unix way by prepending it to `/usr/bin` + { + QStringList pathList = getExecutableSearchPaths(); + if (!pathList.contains("/usr/local/bin")) { + auto idxUsrBin = pathList.indexOf("/usr/bin"); + if (idxUsrBin >= 0) + pathList.insert(idxUsrBin, "/usr/local/bin"); + else + pathList.append("/usr/local/bin"); + } + QString newPath = pathList.join(PATH_SEPARATOR); + qputenv("PATH", newPath.toUtf8()); + } +#endif + QApplication app(argc, argv); app.setAttribute(Qt::AA_UseHighDpiPixmaps); diff --git a/RedPandaIDE/resources/autolink-linux.json b/RedPandaIDE/resources/autolink-xdg.json similarity index 50% rename from RedPandaIDE/resources/autolink-linux.json rename to RedPandaIDE/resources/autolink-xdg.json index f7fd1804..b3584080 100644 --- a/RedPandaIDE/resources/autolink-linux.json +++ b/RedPandaIDE/resources/autolink-xdg.json @@ -1,4 +1,12 @@ [ + { + "header": "fmt/core.h", + "links": "-lfmt" + }, + { + "header": "math.h", + "links": "-lm" + }, { "execUseUTF8": true, "header": "raylib.h", @@ -11,5 +19,13 @@ { "header": "rturtle.h", "links": "-lrturtle" + }, + { + "header": "thread", + "links": "-lpthread" + }, + { + "header": "threads.h", + "links": "-lpthread" } ] diff --git a/RedPandaIDE/settings.cpp b/RedPandaIDE/settings.cpp index bbf38999..bc0bbb21 100644 --- a/RedPandaIDE/settings.cpp +++ b/RedPandaIDE/settings.cpp @@ -203,13 +203,13 @@ QString Settings::Dirs::appResourceDir() const { #ifdef Q_OS_WIN return appDir(); -#elif defined(Q_OS_LINUX) - // in AppImage PREFIX is not true, resolve from relative path - const static QString absoluteResourceDir(QDir(appDir()).absoluteFilePath("../share/" APP_NAME)); - return absoluteResourceDir; #elif defined(Q_OS_MACOS) // return QApplication::instance()->applicationDirPath(); return ""; +#else // XDG desktop + // in AppImage or tarball PREFIX is not true, resolve from relative path + const static QString absoluteResourceDir(QDir(appDir()).absoluteFilePath("../share/" APP_NAME)); + return absoluteResourceDir; #endif } @@ -218,13 +218,13 @@ QString Settings::Dirs::appLibexecDir() const { #ifdef Q_OS_WIN return appDir(); -#elif defined(Q_OS_LINUX) - // in AppImage LIBEXECDIR is not true, resolve from relative path +#elif defined(Q_OS_MACOS) + return QApplication::instance()->applicationDirPath(); +#else // XDG desktop + // in AppImage or tarball LIBEXECDIR is not true, resolve from relative path const static QString relativeLibExecDir(QDir(PREFIX "/bin").relativeFilePath(LIBEXECDIR "/" APP_NAME)); const static QString absoluteLibExecDir(QDir(appDir()).absoluteFilePath(relativeLibExecDir)); return absoluteLibExecDir; -#elif defined(Q_OS_MACOS) - return QApplication::instance()->applicationDirPath(); #endif } @@ -1473,16 +1473,16 @@ void Settings::Editor::doLoad() mRightEdgeLineColor = colorValue("right_edge_line_color",Qt::yellow); //Editor font -#ifdef Q_OS_WIN - mFontName = stringValue("font_name","consolas"); - mNonAsciiFontName = stringValue("non_ascii_font_name","consolas"); -#elif defined(Q_OS_MACOS) - mFontName = stringValue("font_name","Menlo"); - mNonAsciiFontName = stringValue("non_ascii_font_name","PingFang SC"); -#else - mFontName = stringValue("font_name","Dejavu Sans Mono"); - mNonAsciiFontName = stringValue("non_ascii_font_name","Dejavu Sans Mono"); -#endif + mFontName = stringValue("font_name",DEFAULT_MONO_FONT); + QString defaultCjkFontName = CJK_MONO_FONT_SC; + QString defaultLocaleName = QLocale::system().name(); + if (defaultLocaleName == "zh_TW") + defaultCjkFontName = CJK_MONO_FONT_TC; + else if (defaultLocaleName == "ja_JP") + defaultCjkFontName = CJK_MONO_FONT_J; + else if (defaultLocaleName == "ko_KR") + defaultCjkFontName = CJK_MONO_FONT_K; + mNonAsciiFontName = stringValue("non_ascii_font_name",defaultCjkFontName); mFontSize = intValue("font_size",12); mFontOnlyMonospaced = boolValue("font_only_monospaced",true); mLineSpacing = doubleValue("line_spacing",1.0); @@ -1504,11 +1504,7 @@ void Settings::Editor::doLoad() mGutterLineNumbersStartZero = boolValue("gutter_line_numbers_start_zero",false); mGutterUseCustomFont = boolValue("gutter_use_custom_font",false); -#ifdef Q_OS_WIN - mGutterFontName = stringValue("gutter_font_name","consolas"); -#else - mGutterFontName = stringValue("gutter_font_name","Dejavu Sans Mono"); -#endif + mGutterFontName = stringValue("gutter_font_name",DEFAULT_MONO_FONT); mGutterFontSize = intValue("gutter_font_size",12); mGutterFontOnlyMonospaced = boolValue("gutter_font_only_monospaced",true); @@ -2999,11 +2995,11 @@ static void setDebugOptions(Settings::PCompilerSet pSet, bool enableAsan = false pSet->setCompileOption(CC_CMD_OPT_USE_PIPE, COMPILER_OPTION_ON); if (enableAsan) { +#ifdef __aarch64__ + pSet->setCompileOption(CC_CMD_OPT_ADDRESS_SANITIZER, "hwaddress"); +#else pSet->setCompileOption(CC_CMD_OPT_ADDRESS_SANITIZER, "address"); -// pSet->setCustomCompileParams("-fsanitize=address"); -// pSet->setUseCustomCompileParams(true); -// pSet->setCustomLinkParams("-fsanitize=address"); -// pSet->setUseCustomLinkParams(true); +#endif } //Some windows gcc don't correctly support this //pSet->setCompileOption(CC_CMD_OPT_STACK_PROTECTOR, "-strong"); @@ -3064,8 +3060,8 @@ bool Settings::CompilerSets::addSets(const QString &folder, const QString& c_pro } #ifdef Q_OS_LINUX -# if defined(__x86_64__) || __SIZEOF_POINTER__ == 4 - mDefaultIndex = (int)mList.size() - 1; // x86-64 Linux or 32-bit Unix, default to "debug with ASan" +# if defined(__x86_64__) || defined(__aarch64__) || __SIZEOF_POINTER__ == 4 + mDefaultIndex = (int)mList.size() - 1; // x86-64, AArch64 Linux or 32-bit Unix, default to "debug with ASan" # else mDefaultIndex = (int)mList.size() - 2; // other Unix, where ASan can be very slow, default to "debug" # endif @@ -3576,17 +3572,20 @@ void Settings::Environment::doLoad() { //Appearance mTheme = stringValue("theme","dark"); - QString defaultFontName = "Segoe UI"; + QString defaultFontName = DEFAULT_UI_FONT; QString defaultLocaleName = QLocale::system().name(); - if (defaultLocaleName == "zh_CN") { + { QString fontName; -#ifdef Q_OS_WINDOWS - fontName = "Microsoft Yahei"; -#elif defined(Q_OS_MACOS) - fontName = "PingFang SC"; -#elif defined(Q_OS_LINUX) - fontName = "Noto Sans CJK"; -#endif + if (defaultLocaleName == "zh_CN") + fontName = CJK_UI_FONT_SC; + else if (defaultLocaleName == "zh_TW") + fontName = CJK_UI_FONT_TC; + else if (defaultLocaleName == "ja_JP") + fontName = CJK_UI_FONT_J; + else if (defaultLocaleName == "ko_KR") + fontName = CJK_UI_FONT_K; + else + fontName = DEFAULT_UI_FONT; QFont font(fontName); if (font.exactMatch()) { defaultFontName = fontName; @@ -3608,58 +3607,85 @@ void Settings::Environment::doLoad() if (!fileExists(mDefaultOpenFolder)) { mDefaultOpenFolder = QDir::currentPath(); } -#ifdef Q_OS_LINUX -#define SYSTEM_TERMINAL(term) "/usr/bin/" #term, "/usr/local/bin/" #term - const static QString terminals[] { + using AP = TerminalEmulatorArgumentsPattern; + struct TerminalSearchItem { + QString appName; + AP argsPattern; + }; + +#ifdef Q_OS_WINDOWS + const TerminalSearchItem terminals[] { + /* explicitly installed terminals */ + + /* system */ + {"conhost.exe", AP::ImplicitSystem}, // dummy for system default + + /* will not actually be searched, just a list for users who dig into here */ + {"conhost.exe", AP::MinusMinusAppendArgs}, // yes, it accepts GNU-style (--) arguments + {"wt.exe", AP::MinusMinusAppendArgs}, // generally okay, but “Test” does not work + {"alacritty.exe", AP::MinusEAppendArgs}, // GPU-accelerated + {"C:/Program Files/Alacritty/alacritty.exe", AP::MinusEAppendArgs}, + {"C:/Program Files/konsole/bin/konsole.exe", AP::MinusEAppendArgs}, // generally okay, but “Test” does not work + {"C:/Program Files/Git/usr/bin/mintty.exe", AP::MinusEAppendArgs}, // Git Mintty + {"C:/msys64/usr/bin/mintty.exe", AP::MinusEAppendArgs}, // MSYS2 Mintty + }; +#else + const TerminalSearchItem terminals[] { /* modern, specialized or stylized terminal -- user who installed them are likely to prefer them */ - SYSTEM_TERMINAL(alacritty), // GPU-accelerated - SYSTEM_TERMINAL(kitty), // GPU-accelerated - SYSTEM_TERMINAL(wayst), // GPU-accelerated - SYSTEM_TERMINAL(tilix), // tiling - SYSTEM_TERMINAL(cool-retro-term), // old CRT style + {"alacritty", AP::MinusEAppendArgs}, // GPU-accelerated + {"kitty", AP::MinusEAppendArgs}, // GPU-accelerated + {"wayst", AP::MinusEAppendArgs}, // GPU-accelerated - /* default terminal for DE */ - SYSTEM_TERMINAL(konsole), // KDE - SYSTEM_TERMINAL(deepin-terminal), // DDE - SYSTEM_TERMINAL(qterminal), // LXQt - SYSTEM_TERMINAL(lxterminal), // LXDE + {"coreterminal", AP::MinusEAppendCommandLine}, // lightweighted + {"kermit", AP::MinusEAppendCommandLine}, // lightweighted + {"roxterm", AP::MinusEAppendCommandLine}, // lightweighted + {"sakura", AP::MinusEAppendCommandLine}, // lightweighted + {"termit", AP::MinusEAppendArgs}, // Lua scripting + {"termite", AP::MinusEAppendCommandLine}, // tiling, keyboard-centric + {"tilix", AP::MinusEAppendArgs}, // tiling + + {"cool-retro-term", AP::MinusEAppendArgs}, // old CRT style + + /* default terminal for XDG DE -- macOS user who installed them are likely to prefer them */ + {"deepin-terminal", AP::MinusEAppendArgs}, // DDE + {"konsole", AP::MinusEAppendArgs}, // KDE + {"gnome-terminal", AP::MinusMinusAppendArgs}, // GNOME + {"io.elementary.terminal", AP::MinusEAppendCommandLine}, // Pantheon (elementary OS) + {"lxterminal", AP::MinusEAppendArgs}, // LXDE + {"mate-terminal", AP::MinusXAppendArgs}, // MATE + {"qterminal", AP::MinusEAppendArgs}, // LXQt + {"terminator", AP::MinusXAppendArgs}, // tiling, also seen in SBC images + {"terminology", AP::MinusEAppendCommandLine}, // Enlightenment + {"xfce4-terminal", AP::MinusXAppendArgs}, // Xfce /* bundled terminal in AppImage */ - "alacritty", + {"./alacritty", AP::MinusEAppendArgs}, /* compatible, with minor issue */ - SYSTEM_TERMINAL(kgx), // GNOME Console, confirm to quit - SYSTEM_TERMINAL(coreterminal), // not so conforming when parsing args - SYSTEM_TERMINAL(sakura), // not so conforming when parsing args + {"kgx", AP::MinusMinusAppendArgs}, // GNOME Console, confirm to quit - /* compatible, without out-of-box hidpi support */ - SYSTEM_TERMINAL(mlterm), - SYSTEM_TERMINAL(st), - SYSTEM_TERMINAL(terminology), // also not so conforming when parsing args - SYSTEM_TERMINAL(urxvt), - SYSTEM_TERMINAL(xterm), - SYSTEM_TERMINAL(zutty), + /* compatible, without out-of-box hidpi support on Linux */ + {"mlterm", AP::MinusEAppendArgs}, + {"st", AP::MinusEAppendArgs}, + {"urxvt", AP::MinusEAppendArgs}, + {"xterm", AP::MinusEAppendArgs}, + {"zutty", AP::MinusEAppendArgs}, + + /* macOS system */ + {"/Applications/iTerm.app/Contents/MacOS/iTerm2", AP::WriteCommandLineToTempFileThenTempFilename}, + {"/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", AP::WriteCommandLineToTempFileThenTempFilename}, + {"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", AP::WriteCommandLineToTempFileThenTempFilename}, /* fallbacks */ - SYSTEM_TERMINAL(foot), // Wayland only - SYSTEM_TERMINAL(x-terminal-emulator), // Debian alternatives + {"foot", AP::MinusEAppendArgs}, // Wayland only /* parameter incompatible */ - // "gnome-terminal", - // "guake", - // "hyper", - // "io.elementary.terminal", - // "kermit", - // "liri-terminal", - // "mate-terminal", - // "roxterm", - // "station", - // "terminator", - // "termite", - // "tilda", - // "xfce4-terminal", - // "yakuake", + // "guake", // drop down + // "hyper", // no execute support + // "liri-terminal", // no execute support + // "station", // no execute support + // "tilda", // drop down /* incompatible -- other */ // "aterm", // AUR broken, unable to test @@ -3667,22 +3693,57 @@ void Settings::Environment::doLoad() // "rxvt", // no unicode support // "shellinabox", // AUR broken, unable to test }; -#undef SYSTEM_TERMINAL +#endif - auto checkAndSetTerminalPath = [this](QString terminalPath) -> bool { - QDir appDir(pSettings->dirs().appDir()); - QString absoluteTerminalPath = appDir.absoluteFilePath(terminalPath); - QFileInfo termPathInfo(absoluteTerminalPath); - if (termPathInfo.isFile() && termPathInfo.isReadable() && termPathInfo.isExecutable()) { - mTerminalPath = terminalPath; - return true; - } else { - return false; + auto checkAndSetTerminalPath = [this](const TerminalSearchItem &searchItem) -> bool { +#define DO_CHECK_AND_SET do { \ + if (termPathInfo.isFile() && termPathInfo.isReadable() && termPathInfo.isExecutable()) { \ + mTerminalPath = searchItem.appName; \ + mTerminalArgumentsPattern = searchItem.argsPattern; \ + return true; \ + } \ + } while (0) + + switch (getPathUnixExecSemantics(searchItem.appName)) { + case UnixExecSemantics::Absolute: { + QFileInfo termPathInfo(searchItem.appName); + DO_CHECK_AND_SET; + break; } + case UnixExecSemantics::RelativeToCwd: { + QDir appDir(pSettings->dirs().appDir()); + QString absoluteTerminalPath = appDir.absoluteFilePath(searchItem.appName); + QFileInfo termPathInfo(absoluteTerminalPath); + DO_CHECK_AND_SET; + break; + } + case UnixExecSemantics::SearchInPath: { + auto pathList = getExecutableSearchPaths(); + for (auto &dir: pathList) { + QString absoluteTerminalPath = QDir(dir).absoluteFilePath(searchItem.appName); + QFileInfo termPathInfo(absoluteTerminalPath); + DO_CHECK_AND_SET; + } + break; + } + } + return false; }; +#undef DO_CHECK_AND_SET // check saved terminal path - if (!checkAndSetTerminalPath(stringValue("terminal_path", ""))) { + QString savedTerminalPath = stringValue("terminal_path", ""); + int savedArgsPattern_ = intValue("terminal_arguments_pattern", +#ifdef Q_OS_MACOS + // macOS: old versions have set Terminal.app as default terminal + // fallback to temp file to work with Terminal.app for smooth migration + int(AP::WriteCommandLineToTempFileThenTempFilename) +#else + int(AP::MinusEAppendArgs) // Linux: keep old behaviour +#endif + ); + AP savedArgsPattern = static_cast(savedArgsPattern_); + if (!checkAndSetTerminalPath(TerminalSearchItem{savedTerminalPath, savedArgsPattern})) { // if saved terminal path is invalid, try determing terminal from our list for (auto terminal: terminals) { if (checkAndSetTerminalPath(terminal)) @@ -3691,11 +3752,6 @@ void Settings::Environment::doLoad() } mAStylePath = includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"astyle"; -#elif defined(Q_OS_MACOS) - mTerminalPath = stringValue("terminal_path", - "/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal"); - mAStylePath = includeTrailingPathDelimiter(pSettings->dirs().appLibexecDir())+"astyle"; -#endif mHideNonSupportFilesInFileView=boolValue("hide_non_support_files_file_view",true); mOpenFilesInSingleInstance = boolValue("open_files_in_single_instance",false); } @@ -3757,13 +3813,11 @@ QString Settings::Environment::terminalPath() const QString Settings::Environment::terminalPathForExec() const { -#ifdef Q_OS_LINUX - // `mTerminalPath` can be reletive (bundled terminal in AppImage). - QDir appDir(pSettings->dirs().appDir()); - return appDir.absoluteFilePath(mTerminalPath); -#else - return mTerminalPath; -#endif + if (getPathUnixExecSemantics(mTerminalPath) == UnixExecSemantics::RelativeToCwd) { + QDir appDir(pSettings->dirs().appDir()); + return appDir.absoluteFilePath(mTerminalPath); + } else + return mTerminalPath; } void Settings::Environment::setTerminalPath(const QString &terminalPath) @@ -3781,6 +3835,16 @@ void Settings::Environment::setAStylePath(const QString &aStylePath) mAStylePath = aStylePath; } +TerminalEmulatorArgumentsPattern Settings::Environment::terminalArgumentsPattern() const +{ + return mTerminalArgumentsPattern; +} + +void Settings::Environment::setTerminalArgumentsPattern(const TerminalEmulatorArgumentsPattern &argsPattern) +{ + mTerminalArgumentsPattern = argsPattern; +} + bool Settings::Environment::useCustomIconSet() const { return mUseCustomIconSet; @@ -3845,10 +3909,9 @@ void Settings::Environment::doSave() saveValue("current_folder",mCurrentFolder); saveValue("default_open_folder",mDefaultOpenFolder); -#ifndef Q_OS_WIN saveValue("terminal_path",mTerminalPath); + saveValue("terminal_arguments_pattern",int(mTerminalArgumentsPattern)); saveValue("asyle_path",mAStylePath); -#endif saveValue("hide_non_support_files_file_view",mHideNonSupportFilesInFileView); saveValue("open_files_in_single_instance",mOpenFilesInSingleInstance); @@ -4111,13 +4174,7 @@ void Settings::Executor::doLoad() mProblemCaseValidateType =(ProblemCaseValidateType)intValue("problem_case_validate_type", (int)ProblemCaseValidateType::Exact); mRedirectStderrToToolLog = boolValue("redirect_stderr_to_toollog", false); -#ifdef Q_OS_WIN - mCaseEditorFontName = stringValue("case_editor_font_name","consolas"); -#elif defined(Q_OS_MACOS) - mCaseEditorFontName = stringValue("case_editor_font_name", "Menlo"); -#else - mCaseEditorFontName = stringValue("case_editor_font_name","Dejavu Sans Mono"); -#endif + mCaseEditorFontName = stringValue("case_editor_font_name",DEFAULT_MONO_FONT); mCaseEditorFontSize = intValue("case_editor_font_size",11); mCaseEditorFontOnlyMonospaced = boolValue("case_editor_font_only_monospaced",true); int case_timeout = intValue("case_timeout", -1); diff --git a/RedPandaIDE/settings.h b/RedPandaIDE/settings.h index ccf0eb30..6b606e4a 100644 --- a/RedPandaIDE/settings.h +++ b/RedPandaIDE/settings.h @@ -575,6 +575,9 @@ public: QString AStylePath() const; void setAStylePath(const QString &aStylePath); + TerminalEmulatorArgumentsPattern terminalArgumentsPattern() const; + void setTerminalArgumentsPattern(const TerminalEmulatorArgumentsPattern &argsPattern); + bool useCustomIconSet() const; void setUseCustomIconSet(bool newUseCustomIconSet); @@ -606,6 +609,7 @@ public: QString mDefaultOpenFolder; QString mTerminalPath; QString mAStylePath; + TerminalEmulatorArgumentsPattern mTerminalArgumentsPattern; bool mHideNonSupportFilesInFileView; bool mOpenFilesInSingleInstance; // _Base interface diff --git a/RedPandaIDE/settingsdialog/environmentprogramswidget.cpp b/RedPandaIDE/settingsdialog/environmentprogramswidget.cpp index a336a240..43fbc57d 100644 --- a/RedPandaIDE/settingsdialog/environmentprogramswidget.cpp +++ b/RedPandaIDE/settingsdialog/environmentprogramswidget.cpp @@ -19,6 +19,7 @@ #include "../settings.h" #include "../iconsmanager.h" #include "../systemconsts.h" +#include "../compiler/executablerunner.h" #include @@ -27,6 +28,16 @@ EnvironmentProgramsWidget::EnvironmentProgramsWidget(const QString& name, const ui(new Ui::EnvironmentProgramsWidget) { ui->setupUi(this); + QFont monoFont(DEFAULT_MONO_FONT); + ui->rbImplicitSystem->setFont(monoFont); + ui->rbMinusEAppendArgs->setFont(monoFont); + ui->rbMinusXAppendArgs->setFont(monoFont); + ui->rbMinusMinusAppendArgs->setFont(monoFont); + ui->rbMinusEAppendCommandLine->setFont(monoFont); + ui->rbWriteCommandLineToTempFileThenTempFilename->setFont(monoFont); +#ifndef Q_OS_MACOS + hideMacosSpecificPattern(); +#endif } EnvironmentProgramsWidget::~EnvironmentProgramsWidget() @@ -34,14 +45,62 @@ EnvironmentProgramsWidget::~EnvironmentProgramsWidget() delete ui; } +void EnvironmentProgramsWidget::hideMacosSpecificPattern() +{ + ui->rbWriteCommandLineToTempFileThenTempFilename->setVisible(false); + ui->rbWriteCommandLineToTempFileThenTempFilename->setEnabled(false); + ui->pbWriteCommandLineToTempFileThenTempFilename->setVisible(false); + ui->pbWriteCommandLineToTempFileThenTempFilename->setEnabled(false); +} + +void EnvironmentProgramsWidget::testTerminal(const TerminalEmulatorArgumentsPattern &pattern) +{ + auto [filename, arguments, fileOwner] = wrapCommandForTerminalEmulator(ui->txtTerminal->text(), pattern, {defaultShell(), "-c", "echo hello; sleep 3"}); + ExecutableRunner runner(filename, arguments, "", nullptr); + runner.start(); + runner.wait(); +} + void EnvironmentProgramsWidget::doLoad() { ui->txtTerminal->setText(pSettings->environment().terminalPath()); + switch (pSettings->environment().terminalArgumentsPattern()) { + case TerminalEmulatorArgumentsPattern::ImplicitSystem: + ui->rbImplicitSystem->setChecked(true); + break; + case TerminalEmulatorArgumentsPattern::MinusEAppendArgs: + ui->rbMinusEAppendArgs->setChecked(true); + break; + case TerminalEmulatorArgumentsPattern::MinusXAppendArgs: + ui->rbMinusXAppendArgs->setChecked(true); + break; + case TerminalEmulatorArgumentsPattern::MinusMinusAppendArgs: + ui->rbMinusMinusAppendArgs->setChecked(true); + break; + case TerminalEmulatorArgumentsPattern::MinusEAppendCommandLine: + ui->rbMinusEAppendCommandLine->setChecked(true); + break; + case TerminalEmulatorArgumentsPattern::WriteCommandLineToTempFileThenTempFilename: + ui->rbWriteCommandLineToTempFileThenTempFilename->setChecked(true); + break; + } } void EnvironmentProgramsWidget::doSave() { pSettings->environment().setTerminalPath(ui->txtTerminal->text()); + if (ui->rbImplicitSystem->isChecked()) + pSettings->environment().setTerminalArgumentsPattern(TerminalEmulatorArgumentsPattern::ImplicitSystem); + if (ui->rbMinusEAppendArgs->isChecked()) + pSettings->environment().setTerminalArgumentsPattern(TerminalEmulatorArgumentsPattern::MinusEAppendArgs); + if (ui->rbMinusXAppendArgs->isChecked()) + pSettings->environment().setTerminalArgumentsPattern(TerminalEmulatorArgumentsPattern::MinusXAppendArgs); + if (ui->rbMinusMinusAppendArgs->isChecked()) + pSettings->environment().setTerminalArgumentsPattern(TerminalEmulatorArgumentsPattern::MinusMinusAppendArgs); + if (ui->rbMinusEAppendCommandLine->isChecked()) + pSettings->environment().setTerminalArgumentsPattern(TerminalEmulatorArgumentsPattern::MinusEAppendCommandLine); + if (ui->rbWriteCommandLineToTempFileThenTempFilename->isChecked()) + pSettings->environment().setTerminalArgumentsPattern(TerminalEmulatorArgumentsPattern::WriteCommandLineToTempFileThenTempFilename); pSettings->environment().save(); } @@ -61,3 +120,62 @@ void EnvironmentProgramsWidget::on_btnChooseTerminal_clicked() ui->txtTerminal->setText(filename); } } + +void EnvironmentProgramsWidget::on_txtTerminal_textChanged(const QString &terminalPath) +{ + QString terminalPathForExec; + if (getPathUnixExecSemantics(terminalPath) == UnixExecSemantics::RelativeToCwd) { + QDir appDir(pSettings->dirs().appDir()); + terminalPathForExec = appDir.absoluteFilePath(terminalPath); + } else + terminalPathForExec = terminalPath; + QString terminalPathEscaped = escapeArgument(terminalPathForExec, true); + QString shell = defaultShell(); + QStringList execArgs{shell, "-c", "echo hello; sleep 3"}; + + auto displayCommand = [this, &execArgs](const TerminalEmulatorArgumentsPattern &pattern) { + auto [filename, arguments, fileOwner] = wrapCommandForTerminalEmulator(ui->txtTerminal->text(), pattern, execArgs); + for (auto &arg : arguments) + arg = escapeArgument(arg, false); + return escapeArgument(filename, true) + " " + arguments.join(' '); + }; + + ui->rbImplicitSystem->setText(displayCommand(TerminalEmulatorArgumentsPattern::ImplicitSystem)); + ui->rbMinusEAppendArgs->setText(displayCommand(TerminalEmulatorArgumentsPattern::MinusEAppendArgs)); + ui->rbMinusXAppendArgs->setText(displayCommand(TerminalEmulatorArgumentsPattern::MinusXAppendArgs)); + ui->rbMinusMinusAppendArgs->setText(displayCommand(TerminalEmulatorArgumentsPattern::MinusMinusAppendArgs)); + ui->rbMinusEAppendCommandLine->setText(displayCommand(TerminalEmulatorArgumentsPattern::MinusEAppendCommandLine)); + if (ui->rbWriteCommandLineToTempFileThenTempFilename->isEnabled()) + ui->rbWriteCommandLineToTempFileThenTempFilename->setText(displayCommand(TerminalEmulatorArgumentsPattern::WriteCommandLineToTempFileThenTempFilename)); +} + +void EnvironmentProgramsWidget::on_pbImplicitSystem_clicked() +{ + testTerminal(TerminalEmulatorArgumentsPattern::ImplicitSystem); +} + +void EnvironmentProgramsWidget::on_pbMinusEAppendArgs_clicked() +{ + testTerminal(TerminalEmulatorArgumentsPattern::MinusEAppendArgs); +} + +void EnvironmentProgramsWidget::on_pbMinusXAppendArgs_clicked() +{ + testTerminal(TerminalEmulatorArgumentsPattern::MinusXAppendArgs); +} + +void EnvironmentProgramsWidget::on_pbMinusMinusAppendArgs_clicked() +{ + testTerminal(TerminalEmulatorArgumentsPattern::MinusMinusAppendArgs); +} + +void EnvironmentProgramsWidget::on_pbMinusEAppendCommandLine_clicked() +{ + testTerminal(TerminalEmulatorArgumentsPattern::MinusEAppendCommandLine); +} + +void EnvironmentProgramsWidget::on_pbWriteCommandLineToTempFileThenTempFilename_clicked() +{ + testTerminal(TerminalEmulatorArgumentsPattern::WriteCommandLineToTempFileThenTempFilename); +} + diff --git a/RedPandaIDE/settingsdialog/environmentprogramswidget.h b/RedPandaIDE/settingsdialog/environmentprogramswidget.h index 50337757..11c8cbb2 100644 --- a/RedPandaIDE/settingsdialog/environmentprogramswidget.h +++ b/RedPandaIDE/settingsdialog/environmentprogramswidget.h @@ -18,6 +18,7 @@ #define ENVIRONMENTPROGRAMSWIDGET_H #include "settingswidget.h" +#include "utils.h" namespace Ui { class EnvironmentProgramsWidget; @@ -30,6 +31,10 @@ class EnvironmentProgramsWidget : public SettingsWidget public: explicit EnvironmentProgramsWidget(const QString& name, const QString& group, QWidget *parent = nullptr); ~EnvironmentProgramsWidget(); + void hideMacosSpecificPattern(); + +private: + void testTerminal(const TerminalEmulatorArgumentsPattern &pattern); private: Ui::EnvironmentProgramsWidget *ui; @@ -41,6 +46,13 @@ protected: void updateIcons(const QSize &size) override; private slots: void on_btnChooseTerminal_clicked(); + void on_txtTerminal_textChanged(const QString &terminalPath); + void on_pbImplicitSystem_clicked(); + void on_pbMinusEAppendArgs_clicked(); + void on_pbMinusXAppendArgs_clicked(); + void on_pbMinusMinusAppendArgs_clicked(); + void on_pbMinusEAppendCommandLine_clicked(); + void on_pbWriteCommandLineToTempFileThenTempFilename_clicked(); }; #endif // ENVIRONMENTPROGRAMSWIDGET_H diff --git a/RedPandaIDE/settingsdialog/environmentprogramswidget.ui b/RedPandaIDE/settingsdialog/environmentprogramswidget.ui index 7f896b69..91243b04 100644 --- a/RedPandaIDE/settingsdialog/environmentprogramswidget.ui +++ b/RedPandaIDE/settingsdialog/environmentprogramswidget.ui @@ -35,7 +35,141 @@ - + + + + Terminal emulator arguments pattern + + + + + + sh -c "echo hello; sleep 3" + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Test + + + + + + + term -e sh -c "echo hello; sleep 3" + + + + + + + Test + + + + + + + term -x sh -c "echo hello; sleep 3" + + + + + + + Test + + + + + + + term -- sh -c "echo hello; sleep 3" + + + + + + + Test + + + + + + + term -e "sh -c \"echo hello; sleep 3\"" + + + + + + + Test + + + + + + + term /tmp/redpanda_XXXXXX.command + + + + + + + Test + + + + + + + On clicking “Test” for the correct pattern, the terminal emulator + + + + + + + • pops up; + + + + + + + • shows “hello”; and + + + + + + + • quits in 3 seconds. + + + + + + + Qt::Vertical diff --git a/RedPandaIDE/settingsdialog/executorgeneralwidget.cpp b/RedPandaIDE/settingsdialog/executorgeneralwidget.cpp index 5104bca3..1aa3dd1d 100644 --- a/RedPandaIDE/settingsdialog/executorgeneralwidget.cpp +++ b/RedPandaIDE/settingsdialog/executorgeneralwidget.cpp @@ -21,12 +21,15 @@ #include "../systemconsts.h" #include +#include +#include ExecutorGeneralWidget::ExecutorGeneralWidget(const QString& name, const QString& group, QWidget *parent): SettingsWidget(name,group,parent), ui(new Ui::ExecutorGeneralWidget) { ui->setupUi(this); + ui->txtParsedArgsInJson->setFont(QFont(DEFAULT_MONO_FONT)); } ExecutorGeneralWidget::~ExecutorGeneralWidget() @@ -73,3 +76,11 @@ void ExecutorGeneralWidget::updateIcons(const QSize &/*size*/) pIconsManager->setIcon(ui->btnBrowse,IconsManager::ACTION_FILE_OPEN_FOLDER); } + +void ExecutorGeneralWidget::on_txtExecuteParamaters_textChanged(const QString &commandLine) +{ + QStringList parsed = splitProcessCommand(commandLine); + QJsonArray obj = QJsonArray::fromStringList(parsed); + ui->txtParsedArgsInJson->setText(QJsonDocument{obj}.toJson()); +} + diff --git a/RedPandaIDE/settingsdialog/executorgeneralwidget.h b/RedPandaIDE/settingsdialog/executorgeneralwidget.h index 1beb0cae..5e8d1b13 100644 --- a/RedPandaIDE/settingsdialog/executorgeneralwidget.h +++ b/RedPandaIDE/settingsdialog/executorgeneralwidget.h @@ -43,6 +43,8 @@ private slots: void on_btnBrowse_clicked(); // SettingsWidget interface + void on_txtExecuteParamaters_textChanged(const QString &commandLine); + protected: void updateIcons(const QSize &size) override; }; diff --git a/RedPandaIDE/settingsdialog/executorgeneralwidget.ui b/RedPandaIDE/settingsdialog/executorgeneralwidget.ui index bf2c535b..329949c6 100644 --- a/RedPandaIDE/settingsdialog/executorgeneralwidget.ui +++ b/RedPandaIDE/settingsdialog/executorgeneralwidget.ui @@ -55,10 +55,21 @@ true - + + + + + Parsed argv array (represented in JSON): + + + + + + + @@ -75,7 +86,6 @@ - 75 true @@ -88,7 +98,6 @@ - 75 true @@ -101,7 +110,6 @@ - 75 true diff --git a/RedPandaIDE/settingsdialog/settingsdialog.cpp b/RedPandaIDE/settingsdialog/settingsdialog.cpp index 08b8bdd2..1bd35bbf 100644 --- a/RedPandaIDE/settingsdialog/settingsdialog.cpp +++ b/RedPandaIDE/settingsdialog/settingsdialog.cpp @@ -36,6 +36,7 @@ #include "environmentshortcutwidget.h" #include "environmentfolderswidget.h" #include "environmentperformancewidget.h" +#include "environmentprogramswidget.h" #include "executorgeneralwidget.h" #include "executorproblemsetwidget.h" #include "debuggeneralwidget.h" @@ -58,8 +59,7 @@ #include "environmentfileassociationwidget.h" #include "projectversioninfowidget.h" #endif -#ifdef Q_OS_LINUX -#include "environmentprogramswidget.h" +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // XDG desktop #include "formatterpathwidget.h" #endif #include @@ -156,10 +156,8 @@ PSettingsDialog SettingsDialog::optionDialog() widget = new EnvironmentShortcutWidget(tr("Shortcuts"),tr("Environment")); dialog->addWidget(widget); -#ifdef Q_OS_LINUX widget = new EnvironmentProgramsWidget(tr("Terminal"),tr("Environment")); dialog->addWidget(widget); -#endif widget = new EnvironmentPerformanceWidget(tr("Performance"),tr("Environment")); dialog->addWidget(widget); @@ -230,7 +228,7 @@ PSettingsDialog SettingsDialog::optionDialog() widget = new FormatterGeneralWidget(tr("General"),tr("Code Formatter")); dialog->addWidget(widget); -#ifdef Q_OS_LINUX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // XDG desktop widget = new FormatterPathWidget(tr("Program"),tr("Code Formatter")); dialog->addWidget(widget); #endif diff --git a/RedPandaIDE/systemconsts.h b/RedPandaIDE/systemconsts.h index 5244582b..8f89b1db 100644 --- a/RedPandaIDE/systemconsts.h +++ b/RedPandaIDE/systemconsts.h @@ -22,7 +22,6 @@ #define APP_SETTSINGS_FILENAME "redpandacpp.ini" #ifdef Q_OS_WIN #define CONSOLE_PAUSER "consolepauser.exe" -#define ASSEMBLER "nasm.exe" #define GCC_PROGRAM "gcc.exe" #define GPP_PROGRAM "g++.exe" #define GDB_PROGRAM "gdb.exe" @@ -40,9 +39,8 @@ #define SDCC_PROGRAM "sdcc.exe" #define PACKIHX_PROGRAM "packihx.exe" #define MAKEBIN_PROGRAM "makebin.exe" -#elif defined(Q_OS_LINUX) +#else // Unix #define CONSOLE_PAUSER "consolepauser" -#define ASSEMBLER "nasm" #define GCC_PROGRAM "gcc" #define GPP_PROGRAM "g++" #define GDB_PROGRAM "gdb" @@ -61,28 +59,6 @@ #define SDCC_PROGRAM "sdcc" #define PACKIHX_PROGRAM "packihx" #define MAKEBIN_PROGRAM "makebin" -#elif defined(Q_OS_MACOS) -#define CONSOLE_PAUSER "consolepauser" -#define ASSEMBLER "nasm" -#define GCC_PROGRAM "gcc" -#define GPP_PROGRAM "g++" -#define GDB_PROGRAM "gdb" -#define GDB_SERVER_PROGRAM "gdbserver" -#define GDB32_PROGRAM "gdb32" -#define MAKE_PROGRAM "make" -#define WINDRES_PROGRAM "" -#define CLEAN_PROGRAM "rm -rf" -#define CPP_PROGRAM "cpp" -#define GIT_PROGRAM "git" -#define CLANG_PROGRAM "clang" -#define CLANG_CPP_PROGRAM "clang++" -#define LLDB_MI_PROGRAM "lldb-mi" -#define LLDB_SERVER_PROGRAM "lldb-server" -#define SDCC_PROGRAM "sdcc" -#define PACKIHX_PROGRAM "packihx" -#define MAKEBIN_PROGRAM "makebin" -#else -#error "Only support windows, Linux and MacOS now!" #endif #define DEV_PROJECT_EXT "dev" @@ -128,7 +104,7 @@ # define MAKEFILE_NAME "makefile.win" # define XMAKEFILE_NAME "xmake.lua" # define ALL_FILE_WILDCARD "*.*" -#elif defined(Q_OS_LINUX) || defined(Q_OS_MACOS) +#else // Unix # define PATH_SENSITIVITY Qt::CaseSensitive # define PATH_SEPARATOR ":" # define LINE_BREAKER "\n" @@ -142,8 +118,6 @@ # define MAKEFILE_NAME "makefile" # define XMAKEFILE_NAME "xmake.lua" # define ALL_FILE_WILDCARD "*" -#else -#error "Only support windows, linux and macos now!" #endif #define SDCC_IHX_SUFFIX "ihx" @@ -151,6 +125,40 @@ #define SDCC_HEX_SUFFIX "hex" #define SDCC_REL_SUFFIX "rel" +#if defined(Q_OS_WIN) +# define DEFAULT_UI_FONT "Segoe UI" +# define CJK_UI_FONT_SC "Microsoft YaHei UI" +# define CJK_UI_FONT_TC "Microsoft JhengHei UI" +# define CJK_UI_FONT_J "Yu Gothic UI" +# define CJK_UI_FONT_K "Malgun Gothic" +# define DEFAULT_MONO_FONT "Consolas" +# define CJK_MONO_FONT_SC "Microsoft YaHei" +# define CJK_MONO_FONT_TC "Microsoft JhengHei" +# define CJK_MONO_FONT_J "Yu Gothic" +# define CJK_MONO_FONT_K "Malgun Gothic" +#elif defined(Q_OS_MACOS) +# define DEFAULT_UI_FONT "Helvetica Neue" +# define CJK_UI_FONT_SC "PingFang SC" +# define CJK_UI_FONT_TC "PingFang TC" +# define CJK_UI_FONT_J "Hiragino Sans" +# define CJK_UI_FONT_K "Apple SD Gothic Neo" +# define DEFAULT_MONO_FONT "Menlo" +# define CJK_MONO_FONT_SC CJK_UI_FONT_SC +# define CJK_MONO_FONT_TC CJK_UI_FONT_TC +# define CJK_MONO_FONT_J CJK_UI_FONT_J +# define CJK_MONO_FONT_K CJK_UI_FONT_K +#else // XDG desktop +# define DEFAULT_UI_FONT "Sans" // use fontconfig default +# define CJK_UI_FONT_SC "Noto Sans CJK SC" +# define CJK_UI_FONT_TC "Noto Sans CJK TC" +# define CJK_UI_FONT_J "Noto Sans CJK JP" +# define CJK_UI_FONT_K "Noto Sans CJK KR" +# define DEFAULT_MONO_FONT "Monospace" // use fontconfig default +# define CJK_MONO_FONT_SC CJK_UI_FONT_SC // intentionally: the "Mono" version is not stricly monospaced either, and has less weights +# define CJK_MONO_FONT_TC CJK_UI_FONT_TC +# define CJK_MONO_FONT_J CJK_UI_FONT_J +# define CJK_MONO_FONT_K CJK_UI_FONT_K +#endif class SystemConsts { diff --git a/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts b/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts index 9be4beb1..865c7822 100644 --- a/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts +++ b/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts @@ -1958,6 +1958,54 @@ All files (%1) Todos os arquivos (%1) + + Terminal emulator arguments pattern + + + + sh -c "echo hello; sleep 3" + + + + Test + Testar + + + term -e sh -c "echo hello; sleep 3" + + + + term -x sh -c "echo hello; sleep 3" + + + + term -- sh -c "echo hello; sleep 3" + + + + term -e "sh -c \"echo hello; sleep 3\"" + + + + On clicking “Test” for the correct pattern, the terminal emulator + + + + • pops up; + + + + • shows “hello”; and + + + + • quits in 3 seconds. + + + + term /tmp/redpanda_XXXXXX.command + + EnvironmentShortcutModel @@ -2058,6 +2106,10 @@ All files (%1) Todos os arquivos (%1) + + Parsed argv array (represented in JSON): + + ExecutorProblemSetWidget diff --git a/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts b/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts index 68787dff..2acbaf5a 100644 --- a/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts +++ b/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts @@ -2689,7 +2689,72 @@ Are you really want to continue? 终端 - + + Terminal emulator arguments pattern + 终端模拟器参数模式 + + + + sh -c "echo hello; sleep 3" + + + + + + + + + + Test + 测试 + + + + term -e sh -c "echo hello; sleep 3" + + + + + term -x sh -c "echo hello; sleep 3" + + + + + term -- sh -c "echo hello; sleep 3" + + + + + term -e "sh -c \"echo hello; sleep 3\"" + + + + + term /tmp/redpanda_XXXXXX.command + + + + + On clicking “Test” for the correct pattern, the terminal emulator + 点击正确的模式对应的 “测试” 按钮,终端模拟器将会 + + + + • pops up; + • 弹出; + + + + • shows “hello”; and + • 显示 “hello”; + + + + • quits in 3 seconds. + • 并在 3 秒后关闭。 + + + Choose Terminal Program 选择终端程序 @@ -2798,6 +2863,11 @@ Are you really want to continue? Parameters to pass to your program 运行程序的命令行参数 + + + Parsed argv array (represented in JSON): + argv 数组解析结果(以 JSON 表示): + Redirect input to the following file: diff --git a/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts b/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts index fad4ec7e..7ff0aaff 100644 --- a/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts +++ b/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts @@ -1791,6 +1791,54 @@ All files (%1) + + Terminal emulator arguments pattern + + + + sh -c "echo hello; sleep 3" + + + + Test + + + + term -e sh -c "echo hello; sleep 3" + + + + term -x sh -c "echo hello; sleep 3" + + + + term -- sh -c "echo hello; sleep 3" + + + + term -e "sh -c \"echo hello; sleep 3\"" + + + + On clicking “Test” for the correct pattern, the terminal emulator + + + + • pops up; + + + + • shows “hello”; and + + + + • quits in 3 seconds. + + + + term /tmp/redpanda_XXXXXX.command + + EnvironmentShortcutModel @@ -1891,6 +1939,10 @@ All files (%1) + + Parsed argv array (represented in JSON): + + ExecutorProblemSetWidget diff --git a/RedPandaIDE/utils.cpp b/RedPandaIDE/utils.cpp index 83ff9f25..95d5fe02 100644 --- a/RedPandaIDE/utils.cpp +++ b/RedPandaIDE/utils.cpp @@ -447,7 +447,7 @@ void executeFile(const QString &fileName, const QString ¶ms, const QString & { ExecutableRunner* runner=new ExecutableRunner( fileName, - params, + splitProcessCommand(params), workingDir); runner->connect(runner, &QThread::finished, [runner,tempFile](){ @@ -589,3 +589,190 @@ QColor alphaBlend(const QColor &lower, const QColor &upper) { int(lower.blue() * wl + upper.blue() * wu) ); } + +UnixExecSemantics getPathUnixExecSemantics(const QString &path) +{ + QFileInfo pathInfo(path); + if (pathInfo.isRelative()) { + if (path.contains('/')) + return UnixExecSemantics::RelativeToCwd; + else + return UnixExecSemantics::SearchInPath; + } else + return UnixExecSemantics::Absolute; +} + +QStringList getExecutableSearchPaths() +{ + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QString path = env.value("PATH"); + QStringList pathList = path.split(PATH_SEPARATOR); + return pathList; +} + +QString escapeArgument(const QString &arg, [[maybe_unused]] bool isFirstArg) +{ + auto argContainsOneOf = [&arg](auto... ch) { return (arg.contains(ch) || ...); }; + +#ifdef Q_OS_WINDOWS + // See https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess , + // and https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way . + + // TODO: investigate whether we need escaping for cmd. + + if (!arg.isEmpty() && !argContainsOneOf(' ', '\t', '\n', '\v', '"')) + return arg; + + QString result = "\""; + for (auto it = arg.begin(); ; ++it) { + int nBackslash = 0; + while (it != arg.end() && *it == '\\') { + ++it; + ++nBackslash; + } + if (it == arg.end()) { + // Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter. + result.append(QString('\\').repeated(nBackslash * 2)); + break; + } else if (*it == '"') { + // Escape all backslashes and the following double quotation mark. + result.append(QString('\\').repeated(nBackslash * 2 + 1)); + result.push_back(*it); + } else { + // Backslashes aren't special here. + result.append(QString('\\').repeated(nBackslash)); + result.push_back(*it); + } + } + result.push_back('"'); + return result; +#else + /* be speculative, but keep readability. + * ref. https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html + */ + if (arg.isEmpty()) + return R"("")"; + + /* POSIX say the following reserved words (may) have special meaning: + * !, {, }, case, do, done, elif, else, esac, fi, for, if, in, then, until, while, + * [[, ]], function, select, + * only if used as the _first word_ of a command (or somewhere we dot not care). + */ + const static QSet reservedWord{ + "!", "{", "}", "case", "do", "done", "elif", "else", "esac", "fi", "for", "if", "in", "then", "until", "while", + "[[", "]]", "function", "select", + }; + if (isFirstArg && reservedWord.contains(arg)) + return QString(R"("%1")").arg(arg); + + /* POSIX say “shall quote”: + * '|', '&', ';', '<', '>', '(', ')', '$', '`', '\\', '"', '\'', ' ', '\t', '\n'; + * and “may need to be quoted”: + * '*', '?', '[', '#', '~', '=', '%'. + * among which “may need to be quoted” there are 4 kinds: + * - wildcards '*', '?', '[' are “danger anywhere” (handle it as if “shall quote”); + * - comment '#', home '~', is “danger at first char in any word”; + * - (environment) variable '=' is “danger at any char in first word”; + * - foreground '%' is “danger at first char in first word”. + * although not mentioned by POSIX, bash’s brace expansion '{', '}' are also “danger anywhere”. + */ + bool isDoubleQuotingDanger = argContainsOneOf('$', '`', '\\', '"'); + bool isSingleQuotingDanger = arg.contains('\''); + bool isDangerAnyChar = isDoubleQuotingDanger || isSingleQuotingDanger || argContainsOneOf( + '|', '&', ';', '<', '>', '(', ')', ' ', '\t', '\n', + '*', '?', '[', + '{', '}' + ); + bool isDangerFirstChar = (arg[0] == '#') || (arg[0] == '~'); + if (isFirstArg) { + isDangerAnyChar = isDangerAnyChar || arg.contains('='); + isDangerFirstChar = isDangerFirstChar || (arg[0] == '%'); + } + + // a “safe” string + if (!isDangerAnyChar && !isDangerFirstChar) + return arg; + + // prefer more-common double quoting + if (!isDoubleQuotingDanger) + return QString(R"("%1")").arg(arg); + + // and then check the opportunity of single quoting + if (!isSingleQuotingDanger) + return QString("'%1'").arg(arg); + + // escaping is necessary + // use double quoting since it’s less tricky + QString result = "\""; + for (auto ch : arg) { + if (ch == '$' || ch == '`' || ch == '\\' || ch == '"') + result.push_back('\\'); + result.push_back(ch); + } + result.push_back('"'); + return result; + + /* single quoting, which is roughly raw string, is possible and quite simple in programming: + * 1. replace each single quote with `'\''`, which contains + * - a single quote to close quoting, + * - an escaped single quote representing the single quote itself, and + * - a single quote to open quoting again; + * 2. enclose the string with a pair of single quotes. + * e.g. `o'clock` => `'o'\''clock'`, really tricky and hard to read. + */ +#endif +} + +auto wrapCommandForTerminalEmulator(const QString &terminal, const TerminalEmulatorArgumentsPattern &argsPattern, const QStringList &argsWithArgv0) + -> std::tuple> +{ + switch (argsPattern) { + case TerminalEmulatorArgumentsPattern::ImplicitSystem: + default: { + return {argsWithArgv0[0], argsWithArgv0.mid(1), nullptr}; + } + case TerminalEmulatorArgumentsPattern::MinusEAppendArgs: { + return {terminal, QStringList{"-e"} + argsWithArgv0, nullptr}; + } + case TerminalEmulatorArgumentsPattern::MinusXAppendArgs: { + return {terminal, QStringList{"-x"} + argsWithArgv0, nullptr}; + } + case TerminalEmulatorArgumentsPattern::MinusMinusAppendArgs: { + return {terminal, QStringList{"--"} + argsWithArgv0, nullptr}; + } + case TerminalEmulatorArgumentsPattern::MinusEAppendCommandLine: { + QStringList escapedArgs; + for (int i = 0; i < argsWithArgv0.length(); i++) { + auto &arg = argsWithArgv0[i]; + auto escaped = escapeArgument(arg, i == 0); + escapedArgs.append(escaped); + } + return {terminal, QStringList{"-e", escapedArgs.join(' ')}, nullptr}; + } + case TerminalEmulatorArgumentsPattern::WriteCommandLineToTempFileThenTempFilename: { + auto fileOwner = std::make_unique(QDir::tempPath() + "/redpanda_XXXXXX.command"); + if (fileOwner->open()) { + QStringList escapedArgs; + for (int i = 0; i < argsWithArgv0.length(); i++) { + auto &arg = argsWithArgv0[i]; + auto escaped = escapeArgument(arg, i == 0); + escapedArgs.append(escaped); + } + fileOwner->write(escapedArgs.join(' ').toUtf8()); + fileOwner->write(QString('\n').toUtf8()); + fileOwner->flush(); + } + QFile(fileOwner->fileName()).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); + return {terminal, QStringList{fileOwner->fileName()}, std::move(fileOwner)}; + } + } +} + +QString defaultShell() +{ +#ifdef Q_OS_WINDOWS + return "powershell.exe"; +#else + return "sh"; +#endif +} diff --git a/RedPandaIDE/utils.h b/RedPandaIDE/utils.h index d36af8ee..00934181 100644 --- a/RedPandaIDE/utils.h +++ b/RedPandaIDE/utils.h @@ -25,6 +25,7 @@ #include #include #include +#include #define SI_NO_CONVERSION #include "SimpleIni.h" #include "qt_utils/utils.h" @@ -34,6 +35,7 @@ using SimpleIni = CSimpleIniA; using PSimpleIni = std::shared_ptr; +using TemporaryFileOwner = std::unique_ptr; enum class FileType{ GAS, // GNU assembler source file (.s) @@ -113,6 +115,22 @@ enum class ProblemCaseValidateType { IgnoreSpaces }; +enum class UnixExecSemantics { + Absolute, + RelativeToCwd, + SearchInPath, +}; + +enum class TerminalEmulatorArgumentsPattern { + ImplicitSystem = 0, // bash -c "echo hello, world; sleep 3" + MinusEAppendArgs, // term -e bash -c "echo hello, world; sleep 3" # xterm-compatible + MinusXAppendArgs, // term -x bash -c "echo hello, world; sleep 3" # some VTE-based + MinusMinusAppendArgs, // term -- bash -c "echo hello, world; sleep 3" # gnome-terminal, kgx + MinusEAppendCommandLine, // term -e "bash -c \"echo hello, world; sleep 3\"" # some lightweighted; alternative form for VTE-based + + WriteCommandLineToTempFileThenTempFilename = 6226700, // macOS Terminal.app and iTerm2.app; 6226700 is how you dial “macOS00” +}; + FileType getFileType(const QString& filename); QStringList splitProcessCommand(const QString& cmd); @@ -165,4 +183,15 @@ void saveComboHistory(QComboBox* cb,const QString& text); QColor alphaBlend(const QColor &lower, const QColor &upper); +UnixExecSemantics getPathUnixExecSemantics(const QString &path); + +QStringList getExecutableSearchPaths(); + +QString escapeArgument(const QString &arg, bool isFirstArg); + +auto wrapCommandForTerminalEmulator(const QString &terminal, const TerminalEmulatorArgumentsPattern &argsPattern, const QStringList &argsWithArgv0) + -> std::tuple>; + +QString defaultShell(); + #endif // UTILS_H diff --git a/RedPandaIDE/vcs/gitmanager.cpp b/RedPandaIDE/vcs/gitmanager.cpp index c9e5a038..63482e72 100644 --- a/RedPandaIDE/vcs/gitmanager.cpp +++ b/RedPandaIDE/vcs/gitmanager.cpp @@ -25,7 +25,7 @@ void GitManager::createRepository(const QString &folder) contents.append("*.o"); contents.append("*.exe"); contents.append("*.layout"); -#ifdef Q_OS_LINUX +#ifdef Q_OS_UNIX contents.append("*."); #endif @@ -593,7 +593,7 @@ QString GitManager::runGit(const QString& workingFolder, const QStringList &args #ifdef Q_OS_WIN env.insert("PATH",pSettings->dirs().appDir()); env.insert("GIT_ASKPASS",includeTrailingPathDelimiter(pSettings->dirs().appDir())+"redpanda-win-git-askpass.exe"); -#elif defined(Q_OS_LINUX) +#else // Unix env.insert(QProcessEnvironment::systemEnvironment()); env.insert("LANG","en"); env.insert("LANGUAGE","en"); diff --git a/libs/qsynedit/qsynedit/qsynedit.cpp b/libs/qsynedit/qsynedit/qsynedit.cpp index 073a27f3..28ed0924 100644 --- a/libs/qsynedit/qsynedit/qsynedit.cpp +++ b/libs/qsynedit/qsynedit/qsynedit.cpp @@ -56,15 +56,7 @@ QSynEdit::QSynEdit(QWidget *parent) : QAbstractScrollArea(parent), mPaintLock = 0; mPainterLock = 0; mPainting = false; -#ifdef Q_OS_WIN - mFontDummy = QFont("Consolas",12); -#elif defined(Q_OS_LINUX) - mFontDummy = QFont("terminal",14); -#elif defined(Q_OS_MACOS) - mFontDummy = QFont("Menlo", 14); -#else -#error "Not supported!" -#endif + mFontDummy = QFont("monospace",14); mFontDummy.setStyleStrategy(QFont::PreferAntialias); mDocument = std::make_shared(mFontDummy, mFontDummy, this); //fPlugins := TList.Create; diff --git a/libs/qsynedit/qsynedit/syntaxer/asm.cpp b/libs/qsynedit/qsynedit/syntaxer/asm.cpp index 909e690f..0c6723c2 100644 --- a/libs/qsynedit/qsynedit/syntaxer/asm.cpp +++ b/libs/qsynedit/qsynedit/syntaxer/asm.cpp @@ -108,7 +108,7 @@ const QSet ASMSyntaxer::ATTDirectives { ".seh_setframe",".seh_stackalloc",".seh_pushreg", ".seh_savereg",".seh_savemm",".seh_savexmm", ".seh_pushframe",".seh_scope", -#elif defined(Q_OS_LINUX) +#else // Unix ".cfi_sections",".cfi_startproc",".cfi_endproc", ".cfi_personality",".cfi_personality_id",".cfi_fde_data", ".cfi_lsda",".cfi_inline_lsda",".cfi_def_cfa", diff --git a/tools/consolepauser/main.unix.cpp b/tools/consolepauser/main.unix.cpp index d52fdf01..e86c6f40 100644 --- a/tools/consolepauser/main.unix.cpp +++ b/tools/consolepauser/main.unix.cpp @@ -22,6 +22,7 @@ using std::string; using std::vector; #include #include +#include #include #include #include @@ -184,15 +185,11 @@ int main(int argc, char** argv) { //todo: handle error printf("shm open failed %d:%s\n",errno,strerror(errno)); } else { - if (ftruncate(fd_shm,BUF_SIZE)==-1){ - printf("ftruncate failed %d:%s\n",errno,strerror(errno)); - //todo: set size error - } else { - pBuf = (char*)mmap(NULL,BUF_SIZE,PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm,0); - if (pBuf == MAP_FAILED) { - printf("mmap failed %d:%s\n",errno,strerror(errno)); - pBuf = nullptr; - } + // `ftruncate` has already done in RedPandaIDE + pBuf = (char*)mmap(NULL,BUF_SIZE,PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm,0); + if (pBuf == MAP_FAILED) { + printf("mmap failed %d:%s\n",errno,strerror(errno)); + pBuf = nullptr; } }