From 952ce2854ed0d046041963e2fb7a93cb8bec0316 Mon Sep 17 00:00:00 2001 From: Greg Williamson Date: Thu, 12 Mar 2020 23:19:18 +0000 Subject: [PATCH 1/7] actually disable grpc when no grpc --- CMakeLists.txt | 49 +++++++++++++++++++++++++-------------------- MainWindow.cpp | 3 +++ azure-pipelines.yml | 2 +- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 37fe482f4..330a604ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,10 @@ cmake_minimum_required(VERSION 3.11) -include(CMakeDependentOption) -cmake_dependent_option(RGM_ENABLE_GRPC_SERVER "Enable the GRPC client plugin for compilation and code analysis." ON "WIN32" OFF) +if (WIN32) + option(RGM_ENABLE_GRPC_SERVER "Enable the GRPC client plugin for compilation and code analysis." ON) +else() + option(RGM_ENABLE_GRPC_SERVER "Enable the GRPC client plugin for compilation and code analysis." OFF) +endif() if (CMAKE_BUILD_TYPE MATCHES "Debug") set(EXE "RadialGM-Debug") @@ -181,15 +184,15 @@ endif() message(STATUS "Initial build flags:") set(CompilerFlags - CMAKE_C_FLAGS_DEBUG - CMAKE_C_FLAGS_MINSIZEREL - CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_RELWITHDEBINFO - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_RELWITHDEBINFO - ) + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_MINSIZEREL + CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_RELWITHDEBINFO + ) foreach(CompilerFlag ${CompilerFlags}) message(STATUS " '${CompilerFlag}': ${${CompilerFlag}}") @@ -213,17 +216,19 @@ include_directories(${EXE} PRIVATE ${RAPIDJSON_INCLUDE_DIRS}) find_package(yaml-cpp CONFIG REQUIRED) target_link_libraries(${EXE} PRIVATE yaml-cpp) -#Find gRPC -if (MSVC) # Windows actually does something right for once - find_package(gRPC CONFIG REQUIRED) - target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++) -else() # https://tinyurl.com/arch-grpc-bug - find_library(LIB_GRPC_UNSECURE NAMES grpc++_unsecure) - find_library(LIB_GRPC NAMES grpc) - find_library(LIB_GPR NAMES gpr) - find_library(LIB_CARES NAMES cares) - find_library(LIB_ADDRESS_SORTING NAMES address_sorting) - target_link_libraries(${EXE} PRIVATE ${LIB_CARES} ${LIB_ADDRESS_SORTING} ${LIB_GPR} ${LIB_GRPC} ${LIB_GRPC_UNSECURE}) +if (RGM_ENABLE_GRPC_SERVER) + #Find gRPC + if (MSVC) # Windows actually does something right for once + find_package(gRPC CONFIG REQUIRED) + target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++) + else() # https://tinyurl.com/arch-grpc-bug + find_library(LIB_GRPC_UNSECURE NAMES grpc++_unsecure) + find_library(LIB_GRPC NAMES grpc) + find_library(LIB_GPR NAMES gpr) + find_library(LIB_CARES NAMES cares) + find_library(LIB_ADDRESS_SORTING NAMES address_sorting) + target_link_libraries(${EXE} PRIVATE ${LIB_CARES} ${LIB_ADDRESS_SORTING} ${LIB_GPR} ${LIB_GRPC} ${LIB_GRPC_UNSECURE}) + endif() endif() # Find Protobuf diff --git a/MainWindow.cpp b/MainWindow.cpp index bf11f3f83..759480e08 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -20,7 +20,10 @@ #include "Components/Logger.h" #include "Plugins/RGMPlugin.h" + +#ifdef RGM_SERVER_ENABLED #include "Plugins/ServerPlugin.h" +#endif #include "gmk.h" #include "gmx.h" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b6ea26ca9..4331ff4a0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -202,7 +202,7 @@ jobs: echo 'Server = https://mirrors.kernel.org/archlinux/\$repo/os/\$arch' >> /etc/pacman.d/mirrorlist pacman-key --init pacman-key --populate archlinux - pacman -Sy --noconfirm base base-devel git gcc cmake protobuf grpc yaml-cpp pugixml rapidjson boost qt5 qscintilla-qt5 + pacman -Sy --noconfirm base base-devel git gcc cmake protobuf yaml-cpp pugixml rapidjson boost qt5 qscintilla-qt5 EOF displayName: 'Bootstrap Archlinux' From b5fd84c5738aae9f52c247cdc666886051dc951a Mon Sep 17 00:00:00 2001 From: Greg Williamson Date: Fri, 13 Mar 2020 08:02:43 +0000 Subject: [PATCH 2/7] fix grpc on linux --- CMakeLists.txt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 330a604ca..9632e7ef4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,6 @@ cmake_minimum_required(VERSION 3.11) -if (WIN32) - option(RGM_ENABLE_GRPC_SERVER "Enable the GRPC client plugin for compilation and code analysis." ON) -else() - option(RGM_ENABLE_GRPC_SERVER "Enable the GRPC client plugin for compilation and code analysis." OFF) -endif() +option(RGM_ENABLE_GRPC_SERVER "Enable the GRPC client plugin for compilation and code analysis." ON) if (CMAKE_BUILD_TYPE MATCHES "Debug") set(EXE "RadialGM-Debug") @@ -223,11 +219,10 @@ if (RGM_ENABLE_GRPC_SERVER) target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++) else() # https://tinyurl.com/arch-grpc-bug find_library(LIB_GRPC_UNSECURE NAMES grpc++_unsecure) - find_library(LIB_GRPC NAMES grpc) find_library(LIB_GPR NAMES gpr) find_library(LIB_CARES NAMES cares) find_library(LIB_ADDRESS_SORTING NAMES address_sorting) - target_link_libraries(${EXE} PRIVATE ${LIB_CARES} ${LIB_ADDRESS_SORTING} ${LIB_GPR} ${LIB_GRPC} ${LIB_GRPC_UNSECURE}) + target_link_libraries(${EXE} PRIVATE ${LIB_CARES} ${LIB_ADDRESS_SORTING} ${LIB_GPR} ${LIB_GRPC_UNSECURE}) endif() endif() @@ -322,4 +317,4 @@ if (WIN32) fixup_bundle(\"${APPS}\" \"${LIBS}\" \"${SEARCH_PATHS}\") execute_process(COMMAND windeployqt.exe ${WINDEPLOY_ARGS} ${APPS}) ") -endif() +endif() \ No newline at end of file From 7667d4633246eba9490b8d40751063b3cbc92fbe Mon Sep 17 00:00:00 2001 From: Greg Williamson Date: Fri, 13 Mar 2020 17:09:17 +0000 Subject: [PATCH 3/7] update submodule --- Plugins/ServerPlugin.cpp | 3 ++- Submodules/enigma-dev | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Plugins/ServerPlugin.cpp b/Plugins/ServerPlugin.cpp index 389601f5d..c76fc9bb1 100644 --- a/Plugins/ServerPlugin.cpp +++ b/Plugins/ServerPlugin.cpp @@ -272,7 +272,8 @@ ServerPlugin::ServerPlugin(MainWindow& mainWindow) : RGMPlugin(mainWindow) { process->waitForStarted(); // construct the channel and connect to the server running in the process - std::shared_ptr channel = CreateChannel("localhost:37818", InsecureChannelCredentials()); + // Note: gRPC is too dumb to resolve localhost on linux + std::shared_ptr channel = CreateChannel("127.0.0.1:37818", InsecureChannelCredentials()); compilerClient = new CompilerClient(channel, mainWindow); connect(compilerClient, &CompilerClient::CompileStatusChanged, this, &RGMPlugin::CompileStatusChanged); // hookup emake's output to our plugin's output signals so it redirects to the diff --git a/Submodules/enigma-dev b/Submodules/enigma-dev index 8ca715078..c868fabc2 160000 --- a/Submodules/enigma-dev +++ b/Submodules/enigma-dev @@ -1 +1 @@ -Subproject commit 8ca7150780c16611ad43a6bea34b54f0924d6f6c +Subproject commit c868fabc2a66c9bae79b4804ac89b1bdb225eb5d From 592788b488d303672ea0584f06a06e8d95b056ef Mon Sep 17 00:00:00 2001 From: Greg Williamson Date: Sat, 14 Mar 2020 06:36:58 +0000 Subject: [PATCH 4/7] ansi fixes --- CMakeLists.txt | 4 +- Components/ANSIescapeCodeHandler.cpp | 248 +++++++++++++++++++++++++++ Components/ANSIescapeCodeHandler.h | 67 ++++++++ MainWindow.cpp | 19 +- Plugins/RGMPlugin.h | 3 +- Plugins/ServerPlugin.cpp | 7 +- Plugins/ServerPlugin.h | 5 +- RadialGM.pro | 2 + 8 files changed, 349 insertions(+), 6 deletions(-) create mode 100644 Components/ANSIescapeCodeHandler.cpp create mode 100644 Components/ANSIescapeCodeHandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9632e7ef4..b5c88d7ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ set(RGM_SOURCES MainWindow.cpp Dialogs/PreferencesDialog.cpp Dialogs/TimelineChangeMoment.cpp + Components/ANSIescapeCodeHandler.cpp Components/ArtManager.cpp Components/Utility.cpp Components/QMenuView.cpp @@ -83,6 +84,7 @@ set(RGM_HEADERS Dialogs/PreferencesDialog.h Dialogs/PreferencesKeys.h Dialogs/TimelineChangeMoment.h + Components/ANSIescapeCodeHandler.h Components/Logger.h Components/RecentFiles.h Components/QMenuView.h @@ -317,4 +319,4 @@ if (WIN32) fixup_bundle(\"${APPS}\" \"${LIBS}\" \"${SEARCH_PATHS}\") execute_process(COMMAND windeployqt.exe ${WINDEPLOY_ARGS} ${APPS}) ") -endif() \ No newline at end of file +endif() diff --git a/Components/ANSIescapeCodeHandler.cpp b/Components/ANSIescapeCodeHandler.cpp new file mode 100644 index 000000000..8c7b88a85 --- /dev/null +++ b/Components/ANSIescapeCodeHandler.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petar Perisin +** Contact: http://www.qt.io/licensing +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "ANSIescapeCodeHandler.h" + +namespace Utils { + +/*! + \class Utils::AnsiEscapeCodeHandler + \brief The AnsiEscapeCodeHandler class parses text and extracts ANSI escape codes from it. + In order to preserve color information across text segments, an instance of this class + must be stored for the lifetime of a stream. + Also, one instance of this class should not handle multiple streams (at least not + at the same time). + Its main function is parseText(), which accepts text and default QTextCharFormat. + This function is designed to parse text and split colored text to smaller strings, + with their appropriate formatting information set inside QTextCharFormat. + Usage: + \list + \li Create new instance of AnsiEscapeCodeHandler for a stream. + \li To add new text, call parseText() with the text and a default QTextCharFormat. + The result of this function is a list of strings with formats set in appropriate + QTextCharFormat. + \endlist +*/ + +AnsiEscapeCodeHandler::AnsiEscapeCodeHandler() : + m_previousFormatClosed(true) +{ +} + +static QColor ansiColor(uint code) +{ + if (code < 8) return QColor(); + + const int red = code & 1 ? 170 : 0; + const int green = code & 2 ? 170 : 0; + const int blue = code & 4 ? 170 : 0; + return QColor(red, green, blue); +} + +QList AnsiEscapeCodeHandler::parseText(const FormattedText &input) +{ + enum AnsiEscapeCodes { + ResetFormat = 0, + BoldText = 1, + TextColorStart = 30, + TextColorEnd = 37, + RgbTextColor = 38, + DefaultTextColor = 39, + BackgroundColorStart = 40, + BackgroundColorEnd = 47, + RgbBackgroundColor = 48, + DefaultBackgroundColor = 49 + }; + + QList outputData; + + QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat; + + const QString escape = QLatin1String("\x1b["); + const int escapePos = input.text.indexOf(escape); + if (escapePos < 0) { + outputData << FormattedText(input.text, charFormat); + return outputData; + } else if (escapePos != 0) { + outputData << FormattedText(input.text.left(escapePos), charFormat); + } + + const QChar semicolon = QLatin1Char(';'); + const QChar colorTerminator = QLatin1Char('m'); + const QChar eraseToEol = QLatin1Char('K'); + // strippedText always starts with "\e[" + QString strippedText = input.text.mid(escapePos); + while (!strippedText.isEmpty()) { + while (strippedText.startsWith(escape)) { + strippedText.remove(0, 2); + + // \e[K is not supported. Just strip it. + if (strippedText.startsWith(eraseToEol)) { + strippedText.remove(0, 1); + continue; + } + // get the number + QString strNumber; + QStringList numbers; + while (strippedText.at(0).isDigit() || strippedText.at(0) == semicolon) { + if (strippedText.at(0).isDigit()) { + strNumber += strippedText.at(0); + } else { + numbers << strNumber; + strNumber.clear(); + } + strippedText.remove(0, 1); + } + if (!strNumber.isEmpty()) + numbers << strNumber; + + // remove terminating char + if (!strippedText.startsWith(colorTerminator)) { + strippedText.remove(0, 1); + continue; + } + strippedText.remove(0, 1); + + if (numbers.isEmpty()) { + charFormat = input.format; + endFormatScope(); + } + + for (int i = 0; i < numbers.size(); ++i) { + const int code = numbers.at(i).toInt(); + + if (code >= TextColorStart && code <= TextColorEnd) { + charFormat.setForeground(ansiColor(code - TextColorStart)); + setFormatScope(charFormat); + } else if (code >= BackgroundColorStart && code <= BackgroundColorEnd) { + charFormat.setBackground(ansiColor(code - BackgroundColorStart)); + setFormatScope(charFormat); + } else { + switch (code) { + case ResetFormat: + charFormat = input.format; + endFormatScope(); + break; + case BoldText: + charFormat.setFontWeight(QFont::Bold); + setFormatScope(charFormat); + break; + case DefaultTextColor: + charFormat.setForeground(input.format.foreground()); + setFormatScope(charFormat); + break; + case DefaultBackgroundColor: + charFormat.setBackground(input.format.background()); + setFormatScope(charFormat); + break; + case RgbTextColor: + case RgbBackgroundColor: + // See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors + if (++i >= numbers.size()) + break; + switch (numbers.at(i).toInt()) { + case 2: + // RGB set with format: 38;2;;; + if ((i + 3) < numbers.size()) { + (code == RgbTextColor) ? + charFormat.setForeground(QColor(numbers.at(i + 1).toInt(), + numbers.at(i + 2).toInt(), + numbers.at(i + 3).toInt())) : + charFormat.setBackground(QColor(numbers.at(i + 1).toInt(), + numbers.at(i + 2).toInt(), + numbers.at(i + 3).toInt())); + setFormatScope(charFormat); + } + i += 3; + break; + case 5: + // 256 color mode with format: 38;5; + uint index = numbers.at(i + 1).toInt(); + + QColor color; + if (index < 8) { + // The first 8 colors are standard low-intensity ANSI colors. + color = ansiColor(index); + } else if (index < 16) { + // The next 8 colors are standard high-intensity ANSI colors. + color = ansiColor(index - 8).lighter(150); + } else if (index < 232) { + // The next 216 colors are a 6x6x6 RGB cube. + uint o = index - 16; + color = QColor((o / 36) * 51, ((o / 6) % 6) * 51, (o % 6) * 51); + } else { + // The last 24 colors are a greyscale gradient. + uint grey = (index - 232) * 11; + color = QColor(grey, grey, grey); + } + + if (code == RgbTextColor) + charFormat.setForeground(color); + else + charFormat.setBackground(color); + + setFormatScope(charFormat); + ++i; + break; + } + break; + default: + break; + } + } + } + } + + if (strippedText.isEmpty()) + break; + int index = strippedText.indexOf(escape); + if (index > 0) { + outputData << FormattedText(strippedText.left(index), charFormat); + strippedText.remove(0, index); + } else if (index == -1) { + outputData << FormattedText(strippedText, charFormat); + break; + } + } + return outputData; +} + +void AnsiEscapeCodeHandler::endFormatScope() +{ + m_previousFormatClosed = true; +} + +void AnsiEscapeCodeHandler::setFormatScope(const QTextCharFormat &charFormat) +{ + m_previousFormat = charFormat; + m_previousFormatClosed = false; +} + +} // namespace Utils diff --git a/Components/ANSIescapeCodeHandler.h b/Components/ANSIescapeCodeHandler.h new file mode 100644 index 000000000..bcde30c73 --- /dev/null +++ b/Components/ANSIescapeCodeHandler.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petar Perisin +** Contact: http://www.qt.io/licensing +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + + +#ifndef UTILS_ANSIESCAPECODEHANDLER_H +#define UTILS_ANSIESCAPECODEHANDLER_H + +#include + +namespace Utils { + +class FormattedText { +public: + FormattedText() { } + FormattedText(const FormattedText &other) : text(other.text), format(other.format) { } + FormattedText(const QString &txt, const QTextCharFormat &fmt = QTextCharFormat()) : + text(txt), format(fmt) + { } + + QString text; + QTextCharFormat format; +}; + +class AnsiEscapeCodeHandler +{ +public: + AnsiEscapeCodeHandler(); + QList parseText(const FormattedText &input); + void endFormatScope(); + +private: + void setFormatScope(const QTextCharFormat &charFormat); + + bool m_previousFormatClosed; + QTextCharFormat m_previousFormat; +}; + +} // namespace Utils + +#endif // UTILS_ANSIESCAPECODEHANDLER_H diff --git a/MainWindow.cpp b/MainWindow.cpp index 759480e08..8cd6377b3 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -139,7 +139,24 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW #ifdef RGM_SERVER_ENABLED RGMPlugin *pluginServer = new ServerPlugin(*this); auto outputTextBrowser = this->_ui->outputTextBrowser; - connect(pluginServer, &RGMPlugin::LogOutput, outputTextBrowser, &QTextBrowser::append); + connect(pluginServer, &RGMPlugin::LogOutput, [=](const QString& text, const QTextCharFormat &format) { + int startPos = 0; + int crPos = -1; + while ((crPos = text.indexOf('\r', startPos)) >= 0) { + if (text.size() > crPos + 1 && text.at(crPos + 1) == '\n') { + outputTextBrowser->textCursor().insertText(text.mid(startPos, crPos - startPos) + '\n', format); + startPos = crPos + 2; + continue; + } + outputTextBrowser->textCursor().insertText(text.mid(startPos, crPos - startPos), format); + outputTextBrowser->textCursor().clearSelection(); + outputTextBrowser->textCursor().movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + startPos = crPos + 1; + } + if (startPos < text.count()) + outputTextBrowser->textCursor().insertText(text.mid(startPos), format); + }); + connect(pluginServer, &RGMPlugin::CompileStatusChanged, [=](bool finished) { _ui->outputDockWidget->show(); _ui->actionRun->setEnabled(finished); diff --git a/Plugins/RGMPlugin.h b/Plugins/RGMPlugin.h index 282242790..3e7e7b918 100644 --- a/Plugins/RGMPlugin.h +++ b/Plugins/RGMPlugin.h @@ -7,6 +7,7 @@ #include "server.pb.h" #include +#include class RGMPlugin : public QObject { Q_OBJECT @@ -15,7 +16,7 @@ class RGMPlugin : public QObject { ~RGMPlugin(); signals: - void LogOutput(const QString &output); + void LogOutput(const QString &output, const QTextCharFormat &format); void CompileStatusChanged(bool finished = false); public slots: diff --git a/Plugins/ServerPlugin.cpp b/Plugins/ServerPlugin.cpp index c76fc9bb1..6040cbb59 100644 --- a/Plugins/ServerPlugin.cpp +++ b/Plugins/ServerPlugin.cpp @@ -1,5 +1,6 @@ #include "ServerPlugin.h" #include "Widgets/CodeWidget.h" +#include "Components/ANSIescapeCodeHandler.h" #include #include @@ -118,9 +119,13 @@ struct SystemReader : public AsyncReadWorker { struct CompileReader : public AsyncReadWorker { virtual ~CompileReader() {} + Utils::AnsiEscapeCodeHandler ansiHandler; virtual void process(const CompileReply& reply) final { for (auto log : reply.message()) { - emit LogOutput(log.message().c_str()); + QList txts = ansiHandler.parseText(QString::fromStdString(log.message() + "\n")); + for (auto& str : txts) { + emit LogOutput(str.text, str.format); + } } } virtual void finished() final { emit CompileStatusChanged(true); } diff --git a/Plugins/ServerPlugin.h b/Plugins/ServerPlugin.h index 1843e330f..6c1918258 100644 --- a/Plugins/ServerPlugin.h +++ b/Plugins/ServerPlugin.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,7 @@ class CallData : public QObject { signals: void CompileStatusChanged(bool finished = false); - void LogOutput(const QString& output); + void LogOutput(const QString& output, const QTextCharFormat &format); }; class CompilerClient : public QObject { @@ -62,7 +63,7 @@ class CompilerClient : public QObject { signals: void CompileStatusChanged(bool finished = false); - void LogOutput(const QString& output); + void LogOutput(const QString& output, const QTextCharFormat &format); public slots: void UpdateLoop(); diff --git a/RadialGM.pro b/RadialGM.pro index 595d4b070..7e009e792 100644 --- a/RadialGM.pro +++ b/RadialGM.pro @@ -96,6 +96,7 @@ SOURCES += \ Widgets/AssetView.cpp \ Widgets/RoomView.cpp \ Models/TreeModel.cpp \ + Components/ANSIescapeCodeHandler.cpp \ Components/ArtManager.cpp \ Models/ProtoModel.cpp \ Models/ImmediateMapper.cpp \ @@ -140,6 +141,7 @@ HEADERS += \ Widgets/RoomView.h \ Models/TreeModel.h \ Components/Logger.h \ + Components/ANSIescapeCodeHandler.h \ Components/ArtManager.h \ Models/ProtoModel.h \ Models/ImmediateMapper.h \ From 82f414d88c6c9186c23228eae2570eee55acd6c9 Mon Sep 17 00:00:00 2001 From: Robert Colton Date: Sun, 15 Mar 2020 02:19:38 -0400 Subject: [PATCH 5/7] fix text cursor --- MainWindow.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/MainWindow.cpp b/MainWindow.cpp index 8cd6377b3..73f2d4704 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -142,19 +142,23 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW connect(pluginServer, &RGMPlugin::LogOutput, [=](const QString& text, const QTextCharFormat &format) { int startPos = 0; int crPos = -1; + auto cursor = outputTextBrowser->textCursor(); + while ((crPos = text.indexOf('\r', startPos)) >= 0) { if (text.size() > crPos + 1 && text.at(crPos + 1) == '\n') { - outputTextBrowser->textCursor().insertText(text.mid(startPos, crPos - startPos) + '\n', format); + cursor.insertText(text.mid(startPos, crPos - startPos) + '\n', format); startPos = crPos + 2; continue; } - outputTextBrowser->textCursor().insertText(text.mid(startPos, crPos - startPos), format); - outputTextBrowser->textCursor().clearSelection(); - outputTextBrowser->textCursor().movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + cursor.insertText(text.mid(startPos, crPos - startPos), format); + cursor.clearSelection(); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); startPos = crPos + 1; } if (startPos < text.count()) - outputTextBrowser->textCursor().insertText(text.mid(startPos), format); + cursor.insertText(text.mid(startPos), format); + + outputTextBrowser->setTextCursor(cursor); }); connect(pluginServer, &RGMPlugin::CompileStatusChanged, [=](bool finished) { From 5d4577e707a287fb43086456aa7de66b013dda1a Mon Sep 17 00:00:00 2001 From: Robert Colton Date: Sun, 15 Mar 2020 03:47:18 -0400 Subject: [PATCH 6/7] fix misplaced cursor insert bug --- MainWindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MainWindow.cpp b/MainWindow.cpp index 73f2d4704..9b60fd60f 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -144,6 +144,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW int crPos = -1; auto cursor = outputTextBrowser->textCursor(); + if (!cursor.atEnd()) + cursor.movePosition(QTextCursor::End); + while ((crPos = text.indexOf('\r', startPos)) >= 0) { if (text.size() > crPos + 1 && text.at(crPos + 1) == '\n') { cursor.insertText(text.mid(startPos, crPos - startPos) + '\n', format); From 4754f8bc5010584b1f016407cd59fea7dbffc851 Mon Sep 17 00:00:00 2001 From: Robert Colton Date: Sun, 15 Mar 2020 04:21:42 -0400 Subject: [PATCH 7/7] use proper terminal-like scroll anchor --- MainWindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MainWindow.cpp b/MainWindow.cpp index 9b60fd60f..3dcb2005f 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -143,6 +143,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW int startPos = 0; int crPos = -1; auto cursor = outputTextBrowser->textCursor(); + auto vbar = outputTextBrowser->verticalScrollBar(); + const bool atBottom = outputTextBrowser->isReadOnly() ? + vbar->value() >= vbar->maximum() : cursor.atEnd(); if (!cursor.atEnd()) cursor.movePosition(QTextCursor::End); @@ -161,7 +164,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW if (startPos < text.count()) cursor.insertText(text.mid(startPos), format); - outputTextBrowser->setTextCursor(cursor); + if (atBottom) vbar->setValue(vbar->maximum()); }); connect(pluginServer, &RGMPlugin::CompileStatusChanged, [=](bool finished) {